Estou trabalhando em um projeto ( Rails 3.0.15, ruby 1.9.3-p125-perf ) em que o banco de dados está no host local e a tabela de usuários possui um pouco mais de 100 mil registros .
Usando
encomendar por RAND ()
é bem lento
User.order ("RAND (id)"). First
torna-se
SELECT users
. * DA users
ORDEM POR RAND (id) LIMITE 1
e leva de 8 a 12 segundos para responder !!
Log do Rails:
Carga do usuário (11030.8ms) SELECT users
. * DA users
ORDEM POR RAND () LIMITE 1
da explicação do mysql
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
| 1 | SIMPLE | users | ALL | NULL | NULL | NULL | NULL | 110165 | Using temporary; Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
Você pode ver que nenhum índice é usado ( Chaves_Possíveis = NULL ), uma tabela temporária é criada e uma passagem extra é necessária para buscar o valor desejado ( extra = Usando temporário; Usando filesort ).
Por outro lado, dividindo a consulta em duas partes e usando Ruby, temos uma melhoria razoável no tempo de resposta.
users = User.scoped.select(:id);nil
User.find( users.first( Random.rand( users.length )).last )
(; nada para uso do console)
Log do Rails:
ID de SELEÇÃO de Carga do Usuário (25,2 ms) DE SELEÇÃO de users
Carga do Usuário (0,2 ms)
users
. * FROM users
WHERE users
. id
= 106854 LIMITE 1
e a explicação do mysql prova o porquê:
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
| 1 | SIMPLE | users | index | NULL | index_users_on_user_type | 2 | NULL | 110165 | Using index |
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
agora podemos usar apenas índices e a chave primária e executar o trabalho cerca de 500 vezes mais rápido!
ATUALIZAR:
como apontado por icantbecool nos comentários, a solução acima apresenta uma falha se houver registros excluídos na tabela.
Uma solução alternativa para isso pode ser
users_count = User.count
User.scoped.limit(1).offset(rand(users_count)).first
que se traduz em duas consultas
SELECT COUNT(*) FROM `users`
SELECT `users`.* FROM `users` LIMIT 1 OFFSET 148794
e roda em cerca de 500ms.