Je travaille sur un projet ( Rails 3.0.15, ruby 1.9.3-p125-perf ) où se trouve la base de données localhost et la table des utilisateurs a un peu plus de 100K enregistrements .
En utilisant
ordre par RAND ()
est assez lent
User.order ("RAND (id)"). First
devient
SÉLECTIONNER users
. * DEusers
ORDER BY RAND (id) LIMIT 1
et prend de 8 à 12 secondes pour répondre !!
Journal des rails:
Charge utilisateur (11030.8ms) SELECT users
. * FROM users
ORDER BY RAND () LIMIT 1
de mysql's expliquer
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
| 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 |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
Vous pouvez voir qu'aucun index n'est utilisé ( possible_keys = NULL ), une table temporaire est créée et une passe supplémentaire est requise pour récupérer la valeur souhaitée ( extra = Utilisation temporaire; Utilisation de filesort ).
D'autre part, en divisant la requête en deux parties et en utilisant Ruby, nous avons une amélioration raisonnable du temps de réponse.
users = User.scoped.select(:id);nil
User.find( users.first( Random.rand( users.length )).last )
(; nul pour l'utilisation de la console)
Journal des rails:
Charge utilisateur (25,2 ms) SELECT id FROM users
Charge utilisateur (0,2 ms) SELECT
users
. * FROM users
WHERE users
. id
= 106854 LIMITE 1
et mysql's expliquent pourquoi:
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
| 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 | |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
nous pouvons maintenant utiliser uniquement les index et la clé primaire et faire le travail environ 500 fois plus vite!
METTRE À JOUR:
comme indiqué par icantbecool dans les commentaires, la solution ci-dessus présente un défaut s'il y a des enregistrements supprimés dans le tableau.
Une solution de contournement qui peut être
users_count = User.count
User.scoped.limit(1).offset(rand(users_count)).first
ce qui se traduit par deux requêtes
SELECT COUNT(*) FROM `users`
SELECT `users`.* FROM `users` LIMIT 1 OFFSET 148794
et fonctionne en environ 500 ms.