Pracuję nad projektem ( Rails 3.0.15, ruby 1.9.3-p125-perf ), w którym baza danych znajduje się w localhost, a tablica użytkowników ma nieco ponad 100K rekordów .
Za pomocą
order by RAND ()
jest dość powolny
User.order („RAND (id)”). First
staje się
SELECT users
. * FROM users
ORDER BY RAND (id) LIMIT 1
a odpowiedź zajmuje od 8 do 12 sekund !!
Dziennik Railsów:
User Load (11030,8ms) SELECT users
. * FROM users
ORDER BY RAND () LIMIT 1
z wyjaśnienia 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 |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
Widać, że żaden indeks nie jest używany ( possible_keys = NULL ), tworzona jest tymczasowa tabela i wymagane jest dodatkowe przejście, aby pobrać żądaną wartość ( extra = Usingporary; Using filesort ).
Z drugiej strony, dzieląc zapytanie na dwie części i używając Rubiego, mamy znaczną poprawę czasu odpowiedzi.
users = User.scoped.select(:id);nil
User.find( users.first( Random.rand( users.length )).last )
(; zero w przypadku użycia konsoli)
Dziennik Railsów:
User Load (25,2 ms) SELECT id FROM User Load (0,2 ms users
) SELECT
users
. * FROM users
WHERE users
. id
= 106854 LIMIT 1
a wyjaśnienie mysql udowadnia, dlaczego:
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
| 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 | |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
możemy teraz używać tylko indeksów i klucza podstawowego i wykonywać to zadanie około 500 razy szybciej!
AKTUALIZACJA:
jak zauważył icantbecool w komentarzach powyższe rozwiązanie ma wadę, jeśli w tabeli są usunięte rekordy.
Może to być obejście
users_count = User.count
User.scoped.limit(1).offset(rand(users_count)).first
co przekłada się na dwa zapytania
SELECT COUNT(*) FROM `users`
SELECT `users`.* FROM `users` LIMIT 1 OFFSET 148794
i działa w około 500 ms.