Я працюю над проектом ( Rails 3.0.15, ruby 1.9.3-p125-perf ), де db знаходиться в localhost, а таблиця користувачів має трохи більше 100K записів .
Використання
замовлення RAND ()
йде досить повільно
User.order ("RAND (id)")
стає
ВИБІР users
. * ВІД users
ЗАМОВЛЕННЯ ПО РАНДІ (id) ГРАНІ 1
і займає від 8 до 12 секунд щоб відповісти !!
Журнал рейок:
Навантаження користувача (11030,8 мс) ВИБІР users
. * З users
ЗАМОВЛЕННЯ ПО РАНДУ () ГРАНІ 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 |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
Ви можете бачити, що не використовується індекс ( можливі_keys = NULL ), створюється тимчасова таблиця і потрібен додатковий пропуск для отримання потрібного значення ( extra = Використання тимчасового; Використання файлуort ).
З іншого боку, розділивши запит на дві частини та використовуючи Ruby, ми маємо розумне покращення часу відповіді.
users = User.scoped.select(:id);nil
User.find( users.first( Random.rand( users.length )).last )
(; нуль для використання консолі)
Журнал рейок:
Навантаження користувача (25.2ms) ВИБРАТИ ідентифікатор з users
користувальницької навантаження (0,2 мс) ВИБРАТИ
users
. * FROM users
WHERE 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 разів швидше!
ОНОВЛЕННЯ:
як вказує 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 мс.