Как работает порядок MySQL по RAND ()?

Я делал некоторые исследования и испытания о том, как сделать быстрый случайный выбор в MySQL. В процессе я столкнулся с неожиданными результатами, и теперь я не совсем уверен, что знаю, как работает ORDER BY RAND ().

Я всегда думал, что когда вы делаете заказ RAND() на таблице, MySQL добавляет новый столбец в таблицу, которая заполнена случайными значениями, затем он сортирует данные по этому столбцу, а затем, например, вы берете вышеуказанное значение, которое попало туда случайно. Я сделал много погуглить и тестирование и, наконец, обнаружили, что запрос Джей предлагает в своем блоге - это действительно самое быстрое решение:

SELECT * FROM Table T JOIN (SELECT CEIL(MAX(ID)*RAND()) AS ID FROM Table) AS x ON T.ID >= x.ID LIMIT 1;

в то время как общий порядок RAND() занимает 30-40 секунд в моей тестовой таблице, его запрос выполняет работу за 0,1 секунды. Он объясняет, как это работает в блоге, поэтому я просто пропущу это и, наконец, перейду к странной вещи.

моя таблица является общей таблицей с первичным ключом id и другие неиндексированные вещи, как username, age, etc. Вот в чем дело Я изо всех сил пытаюсь объяснить

SELECT * FROM table ORDER BY RAND() LIMIT 1; /*30-40 seconds*/
SELECT id FROM table ORDER BY RAND() LIMIT 1; /*0.25 seconds*/
SELECT id, username FROM table ORDER BY RAND() LIMIT 1; /*90 seconds*/

Я ожидал увидеть примерно одно и то же время для всех трех запросов, так как я всегда сортирую по одному столбцу. Но почему-то этого не произошло. Пожалуйста, дайте мне знать, если у вас есть какие-либо идеи об этом. У меня есть проект, где мне нужно сделать быстрый заказ RAND (), и лично я предпочел бы использовать

SELECT id FROM table ORDER BY RAND() LIMIT 1;
SELECT * FROM table WHERE id=ID_FROM_PREVIOUS_QUERY LIMIT 1;

, который, Да, работает медленнее, чем метод Джея, однако он меньше и легче понять. Мои запросы довольно большие с несколькими соединениями и с предложением WHERE и в то время как метод Джея все еще работает, запрос становится очень большим и сложным, потому что мне нужно использовать все соединения и где в Объединенном (называемом x в его запросе) подзапросе.

Спасибо за ваше время!

4 ответов


Хотя нет такой вещи, как "быстрый заказ rand ()", существует обходной путь для вашей конкретной задачи.

для получения любой случайной строки, вы можете сделать как этот немецкий блоггер делает:http://www.roberthartung.de/mysql-order-by-rand-a-case-study-of-alternatives/ (я не видел url-адрес горячей ссылки. Если кто-то увидит его, не стесняйтесь редактировать ссылку.)

текст на немецком языке, но код SQL немного вниз по странице и большими белыми коробки, так что их нетрудно увидеть.

В основном то, что он делает, это сделать процедуру, которая выполняет работу по получению действительной строки. Это генерирует случайное число между 0 и max_id, попробуйте получить строку, и если она не существует, продолжайте идти, пока не нажмете тот, который делает. Он позволяет получать x случайных строк, сохраняя их во временной таблице, поэтому вы, вероятно, можете переписать процедуру, чтобы быть немного быстрее, получая только одну строку.

недостатком этого является то, что если вы удалите Много строк, и есть огромные пробелы, велики шансы, что он пропустит тонны раз, что делает его неэффективным.

обновление: разное время выполнения

выберите * из порядка таблицы по RAND () предел 1;/30-40 секунд/

выберите id из порядка таблицы по RAND () LIMIT 1;/0,25 секунды/

выберите id, имя пользователя из порядка таблицы по RAND () LIMIT 1;/90 секунды!--24-->/

Я ожидал увидеть примерно одно и то же время для всех трех запросов, так как я всегда сортирую по одному столбцу. Но почему-то этого не произошло. Пожалуйста, дайте мне знать, если у вас есть какие-либо идеи об этом.

это может иметь отношение к индексации. id индексируется и быстро получить доступ, в то время как добавление username в результате, значит, он должен читать, что из каждой строки и поместить его в таблицу памяти. С * Она также имеет чтобы прочитать все в память, но ему не нужно прыгать вокруг файла данных, что означает, что нет времени на поиски.

это имеет значение только в том случае, если есть столбцы переменной длины (varchar/text), что означает, что он должен проверить длину, а затем пропустить эту длину, а не просто пропустить заданную длину (или 0) между каждой строкой.


Это может быть связано с индексацией. id индексированный и быстрый доступ к, тогда как добавление имени пользователя в результат, означает он должен прочитать это из каждой строки и положи в таблицу памяти. С * он также должен читать все в память, но ей не нужно перейти вокруг файла данных, что означает нет времени на поиски. Этот имеет значение только если есть столбцы переменной длины, что означает, что он должен проверить длину, а затем пропустить эта длина, в отличие от просто пропуск заданной длины (или 0) между каждый ряд

практика лучше, чем все теории! Почему бы просто не проверить планы? :)

mysql> explain select name from avatar order by RAND() limit 1;
+----+-------------+--------+-------+---------------+-----------------+---------+------+-------+----------------------------------------------+
| id | select_type | table  | type  | possible_keys | key             | key_len | ref  | rows  | Extra                                        |
+----+-------------+--------+-------+---------------+-----------------+---------+------+-------+----------------------------------------------+
|  1 | SIMPLE      | avatar | index | NULL          | IDX_AVATAR_NAME | 302     | NULL | 30062 | Using index; Using temporary; Using filesort |
+----+-------------+--------+-------+---------------+-----------------+---------+------+-------+----------------------------------------------+
1 row in set (0.00 sec)

mysql> explain select * from avatar order by RAND() limit 1;
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows  | Extra                           |
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
|  1 | SIMPLE      | avatar | ALL  | NULL          | NULL | NULL    | NULL | 30062 | Using temporary; Using filesort |
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
1 row in set (0.00 sec)

 mysql> explain select name, experience from avatar order by RAND() limit 1;
+----+-------------+--------+------+--------------+------+---------+------+-------+---------------------------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows  | Extra                           |
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
|  1 | SIMPLE      | avatar | ALL  | NULL          | NULL | NULL    | NULL | 30064 | Using temporary; Using filesort |
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+

Я могу сказать вам, почему SELECT id FROM ... намного медленнее, чем два других, но я не уверен, почему SELECT id, username в 2-3 раза быстрее, чем SELECT *.

когда у вас есть индекс (первичный ключ в вашем случае), и результат включает только столбцы из индекса, MySQL optimizer может использовать данные только из индекса, даже не заглядывает в саму таблицу. Чем дороже каждая строка, тем больший эффект вы будете наблюдать, так как вы заменяете операции ввода-вывода файловой системы чистые операции в памяти. Если у вас будет дополнительный индекс (id, username), у вас будет аналогичная производительность и в третьем случае.


почему бы вам не добавить индекс id, username в таблице см., Если это заставляет mysql использовать индекс, а не просто файловую и временную таблицу.