Estoy trabajando en un proyecto ( Rails 3.0.15, ruby 1.9.3-p125-perf ) donde el db está en localhost y la tabla de usuarios tiene un poco más de 100K registros .
Utilizando
ordenar por RAND ()
es bastante lento
User.order ("RAND (id)"). First
se convierte
SELECCIONAR users
. * DE users
ORDEN POR RAND (id) LÍMITE 1
y toma de 8 a 12 segundos para responder !!
Registro de rieles:
Carga del usuario (11030.8ms) SELECCIONAR users
. * DE users
ORDEN POR RAND () LÍMITE 1
de explicar 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 |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
Puede ver que no se utiliza ningún índice ( possible_keys = NULL ), se crea una tabla temporal y se requiere un pase adicional para obtener el valor deseado ( extra = Uso temporal; Uso de clasificación de archivos ).
Por otro lado, al dividir la consulta en dos partes y usar Ruby, tenemos una mejora razonable en el tiempo de respuesta.
users = User.scoped.select(:id);nil
User.find( users.first( Random.rand( users.length )).last )
(; nulo para uso de consola)
Registro de rieles:
User Load (25.2ms) SELECT id FROM users
User Load (0.2ms) SELECT
users
. * FROM users
WHERE users
. id
= 106854 LÍMITE 1
y la explicación de mysql prueba por qué:
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
| 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 | |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
¡ahora solo podemos usar índices y la clave principal y hacer el trabajo unas 500 veces más rápido!
ACTUALIZAR:
Como lo señala icantbecool en los comentarios, la solución anterior tiene un defecto si hay registros eliminados en la tabla.
Una solución alternativa en eso puede ser
users_count = User.count
User.scoped.limit(1).offset(rand(users_count)).first
que se traduce en dos consultas
SELECT COUNT(*) FROM `users`
SELECT `users`.* FROM `users` LIMIT 1 OFFSET 148794
y se ejecuta en unos 500 ms.