Ich arbeite an einem Projekt ( Rails 3.0.15, Ruby 1.9.3-p125-perf ), bei dem sich die Datenbank in localhost befindet und die Benutzertabelle etwas mehr als 100.000 Datensätze enthält .
Verwenden von
Bestellung per RAND ()
ist ziemlich langsam
User.order ("RAND (id)"). Zuerst
wird
SELECT users
. * FROM users
ORDER BY RAND (id) LIMIT 1
und dauert 8 bis 12 Sekunden zu antworten !!
Schienenprotokoll:
Benutzerlast (11030,8 ms) SELECT users
. * FROM users
ORDER BY RAND () LIMIT 1
von mysql erklären
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
| 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 |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
Sie können sehen, dass kein Index verwendet wird ( simply_keys = NULL ), eine temporäre Tabelle erstellt wird und ein zusätzlicher Durchgang erforderlich ist, um den gewünschten Wert abzurufen ( extra = Temporär verwenden; Dateisortierung verwenden ).
Auf der anderen Seite haben wir durch die Aufteilung der Abfrage in zwei Teile und die Verwendung von Ruby eine angemessene Verbesserung der Antwortzeit.
users = User.scoped.select(:id);nil
User.find( users.first( Random.rand( users.length )).last )
(; Null für Konsolengebrauch)
Schienenprotokoll:
Benutzerlast (25,2 ms) SELECT id FROM users
Benutzerlast (0,2 ms) SELECT
users
. * FROM users
WHERE users
. id
= 106854 GRENZWERT 1
und mysqls erklären beweist warum:
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
| 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 | |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
Wir können jetzt nur noch Indizes und den Primärschlüssel verwenden und die Arbeit ungefähr 500 Mal schneller erledigen!
AKTUALISIEREN:
Wie von icantbecool in den Kommentaren hervorgehoben, weist die obige Lösung einen Fehler auf, wenn gelöschte Datensätze in der Tabelle enthalten sind.
Eine Problemumgehung kann sein
users_count = User.count
User.scoped.limit(1).offset(rand(users_count)).first
was zu zwei Abfragen übersetzt
SELECT COUNT(*) FROM `users`
SELECT `users`.* FROM `users` LIMIT 1 OFFSET 148794
und läuft in ca. 500ms.