Я работаю над проектом ( Rails 3.0.15, ruby 1.9.3-p125-perf ), где БД находится в localhost , а таблица пользователей имеет чуть больше 100K записей .
Использование
заказ по RAND ()
довольно медленно
User.order ( "RAND (идентификатор)"). Первый
становится
ВЫБРАТЬ users
. * ОТ users
ЗАКАЗАТЬ ПО RAND (id) LIMIT 1
и занимает от 8 до 12 секунд , чтобы ответить !!
Журнал рельсов:
Пользовательская нагрузка (11030,8 мс) SELECT users
. * ОТ users
ЗАКАЗАТЬ ПО RAND ()
LIMIT 1
из объяснения 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 |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
Вы видите, что индекс не используется ( возможный_ключ = NULL ), создается временная таблица и требуется дополнительный проход для извлечения желаемого значения ( extra = Использование временного; Использование файловой сортировки * * тысяча сорок-девять).
С другой стороны, разделив запрос на две части и используя Ruby, мы получаем разумное улучшение времени отклика.
users = User.scoped.select(:id);nil
User.find( users.first( Random.rand( users.length )).last )
(; ноль для консольного использования)
Журнал рельсов:
Пользовательская нагрузка (25,2 мс) SELECT id ОТ users
Пользовательская нагрузка (0,2 мс) SELECT
users
. * ОТ users
ГДЕ users
. id
= 106854 ПРЕДЕЛ 1
и объяснение MySQL доказывает, почему:
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
| 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 | |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
теперь мы можем использовать только индексы и первичный ключ и выполнять работу примерно в 500 раз быстрее!
UPDATE:
, как указано в комментариях icantbecool, вышеупомянутое решение имеет недостаток, если в таблице есть удаленные записи.
Обходной путь, который может быть
users_count = User.count
User.scoped.limit(1).offset(rand(users_count)).first
, что переводится на два запроса
SELECT COUNT(*) FROM `users`
SELECT `users`.* FROM `users` LIMIT 1 OFFSET 148794
и работает примерно за 500 мс.