Для наиболее общего случая вот как вы это делаете:
SELECT nameFROM random AS r1 JOIN(SELECT CEIL(RAND()*(SELECT MAX(id)FROM random))AS id)AS r2WHERE r1.id >= r2.idORDERBY r1.id ASC
LIMIT 1
Это предполагает, что распределение идентификаторов одинаково, и что в списке идентификаторов могут быть пробелы. Смотрите статью для более продвинутых примеров
Да, если у вас есть потенциально большие пробелы в идентификаторах, то вероятность случайного выбора вашего самого низкого идентификатора намного ниже, чем вашего высокого идентификатора. На самом деле вероятность того, что первое удостоверение личности после получения самого большого пробела, на самом деле самая высокая. Поэтому это не случайно по определению.
lukeocodes
6
Как вы получаете 10 разных случайных строк? Нужно ли устанавливать ограничение в 10, а затем повторять с 10 раз mysqli_fetch_assoc($result)? Или эти 10 результатов не обязательно различимы?
Адам
12
Случайное требует равных шансов для любого результата, на мой взгляд. ;)
lukeocodes
4
В полной статье рассматриваются такие проблемы, как неравное распределение и повторяющиеся результаты.
Брэд Сзонье
1
в частности, если у вас есть пробел в начале ваших идентификаторов, первый будет выбран (мин / макс-мин) времени. Для этого случая простой твик - это MAX () - MIN () * RAND + MIN (), что не слишком медленно.
Mateusz - proof pls, SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10занимает 0,0010, без LIMIT 10 - 0,0012 (в этой таблице 3500 слов).
Артур Кушман
26
@zeusakm 3500 слов не так уж много; проблема в том, что он взрывается после определенной точки, потому что MySQL должен фактически сортировать ВСЕ записи после прочтения каждой; как только эта операция коснется жесткого диска, вы почувствуете разницу.
Ja͢ck
16
Я не хочу повторяться, но опять же, это полное сканирование таблицы. На больших таблицах это занимает много времени и памяти и может привести к созданию & операции с временной таблицей на диске, что очень медленно.
Мэтт
10
Когда я брал интервью у Facebook в 2010 году, меня спросили, как выбрать случайную запись из огромного файла неизвестного размера за одно чтение. Как только вы придумали идею, ее легко обобщить для выбора нескольких записей. Так что да, сортировка всего файла смешна. В то же время это очень удобно. Я просто использовал этот подход, чтобы выбрать 10 случайных строк из таблицы с более чем 1 000 000 строк. Конечно, мне пришлось немного подождать; но я просто хотел понять, как выглядят типичные строки в этой таблице ...
osa
27
Простой запрос, который имеет отличную производительность и работает с пробелами :
SELECT*FROM tbl AS t1 JOIN(SELECT id FROM tbl ORDERBY RAND() LIMIT 10)as t2 ON t1.id=t2.id
Этот запрос на 200K таблице занимает 0.08s и нормальную версию (SELECT * FROM TBL ORDER BY RAND () LIMIT 10) принимает 0.35s на моей машине.
Это быстро, потому что на этапе сортировки используется только индексированный столбец идентификаторов. Вы можете увидеть это поведение в объяснении:
ВЫБЕРИТЕ * ОТ ТАБЛИЧНОГО ЗАКАЗА ПО RAND () LIMIT 10:
ВЫБЕРИТЕ * ОТ tbl КАК t1 ПРИСОЕДИНЯЙТЕСЬ (ВЫБЕРИТЕ идентификатор ОТ tbl ЗАКАЗАТЬ ПО RAND () LIMIT 10) при t2 ON t1.id = t2.id
Извините, я проверил! низкая производительность на 600 тыс. записей.
Дилан Б
@DylanB Я обновил ответ тестом.
Али
17
Я получаю быстрые запросы (около 0,5 секунд) с медленным процессором , выбирая 10 случайных строк в регистрах 400 КБ базы данных MySQL без кэширования размером 2 ГБ. Смотрите здесь мой код: Быстрый выбор случайных строк в MySQL
<?php
$time= microtime_float();$sql='SELECT COUNT(*) FROM pages';$rquery= BD_Ejecutar($sql);
list($num_records)=mysql_fetch_row($rquery);
mysql_free_result($rquery);$sql="SELECT id FROM pages WHERE RAND()*$num_records<20
ORDER BY RAND() LIMIT 0,10";$rquery= BD_Ejecutar($sql);while(list($id)=mysql_fetch_row($rquery)){if($id_in)$id_in.=",$id";else$id_in="$id";}
mysql_free_result($rquery);$sql="SELECT id,url FROM pages WHERE id IN($id_in)";$rquery= BD_Ejecutar($sql);while(list($id,$url)=mysql_fetch_row($rquery)){
logger("$id, $url",1);}
mysql_free_result($rquery);$time= microtime_float()-$time;
logger("num_records=$num_records",1);
logger("$id_in",1);
logger("Time elapsed: <b>$time segundos</b>",1);?>
Учитывая мою таблицу с более чем 14 миллионами записей, это так же медленно, какORDER BY RAND()
Фабрицио
5
@snippetsofcode В вашем случае - 400 тыс. строк, которые вы можете использовать просто «ORDER BY rand ()». Ваш трюк с 3 запросами бесполезен. Вы можете переписать его как «ВЫБЕРИТЕ ИД, URL-адрес ОТ страниц, ГДЕ ИДЕНТИФИКАЦИЯ (ВЫБЕРИТЕ ИД ИЗ СТРАНЫ, ЗАКАЗАТЬ rand () LIMIT 10)»
Роман Подлинов
4
Ваша техника все еще выполняет сканирование таблицы. Используйте, FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';чтобы увидеть это.
Рик Джеймс
4
Также попробуйте выполнить этот запрос на веб-странице 200 req / s. Параллелизм убьет тебя.
Marki555
Преимущество @RomanPodlinov по сравнению с обычным ORDER BY RAND()заключается в том, что он сортирует только идентификаторы (не полные строки), поэтому временная таблица меньше, но все равно должна сортировать их все.
К вашему сведению, order by rand()очень медленно, если стол большой
evilReiko
6
Иногда МЕДЛЕННОЕ принимается, если я хочу сохранить его
Индексирование должно применяться к таблице, если оно большое.
Мухаммед Азим
1
Индексация здесь не поможет. Индексы полезны для очень конкретных вещей, и этот запрос не является одним из них.
Эндрю
13
Из книги:
Выберите случайную строку, используя смещение
Еще один метод, который позволяет избежать проблем, обнаруженных в предыдущих альтернативах, заключается в подсчете строк в наборе данных и возврате случайного числа между 0 и счетчиком. Затем используйте это число в качестве смещения при запросе набора данных
Это помогает некоторым для MyISAM, но не для InnoDB (при условии, что id является кластеризованным PRIMARY KEY).
Рик Джеймс
7
Хорошо, если у вас нет пробелов в ваших ключах, и они все числовые, вы можете вычислить случайные числа и выбрать эти строки. но это, вероятно, не так.
это в основном гарантирует, что вы получите случайное число в диапазоне ваших ключей, а затем выберете следующий лучший, который больше. Вы должны сделать это 10 раз.
однако это не случайно, потому что ваши ключи, скорее всего, не будут распределяться равномерно.
Это действительно большая проблема, и ее нелегко решить, выполнив все требования, MySQL rand () - лучшее, что вы можете получить, если вам действительно нужно 10 случайных строк.
Можете ли вы объяснить немного больше, чтобы я мог дать вам хорошее решение.
Например, у компании, с которой я работал, было решение, в котором они нуждались в абсолютной случайности очень быстро. Они закончили с предварительным заполнением базы данных случайными значениями, которые были выбраны по убыванию и впоследствии снова установлены на разные случайные значения.
Если вы вряд ли когда-либо обновите, вы также можете заполнить инкрементный идентификатор, чтобы у вас не было пробелов и вы могли просто вычислить случайные ключи перед выбором ... Это зависит от варианта использования!
Привет джо В этом конкретном случае у ключей не должно быть пробелов, но со временем это может измениться. И пока ваш ответ работает, он сгенерирует случайные 10 строк (при условии, что я напишу предел 10), которые являются последовательными, и я хотел, так сказать, больше случайности. :) Спасибо.
Франциск
Если вам нужно 10, используйте некое объединение для генерации 10 уникальных строк.
Джоно
что я сказал. вам нужно выполнить это 10 раз. объединение его с объединением - один из способов поместить его в один запрос. см. мое приложение 2 минуты назад.
Surrican
1
@TheSurrican, это решение выглядит круто, но очень некорректно . Попробуйте вставить только один очень большой, Idи все ваши случайные запросы вернут вам этот Id.
Pacerier
1
FLOOR(RAND()*MAX(id))склонен к возвращению больших идентификаторов.
Рик Джеймс
3
Мне нужен был запрос, чтобы вернуть большое количество случайных строк из довольно большой таблицы. Это то, что я придумал. Сначала получите максимальный идентификатор записи:
SELECT MAX(id)FROM table_name;
Затем подставьте это значение в:
SELECT*FROM table_name WHERE id > FLOOR(RAND()* max) LIMIT n;
Где max - максимальный идентификатор записи в таблице, а n - количество строк, которые вы хотите в вашем наборе результатов. Предполагается, что в идентификаторах записей нет пробелов, хотя я сомневаюсь, что это повлияет на результат, если они были (хотя я и не пробовал). Я также создал эту хранимую процедуру, чтобы быть более общей; передайте имя таблицы и количество возвращаемых строк. Я использую MySQL 5.5.38 в Windows 2008, 32 ГБ, двойной EHz50 с частотой 3 ГГц и в таблице с 17 361 264 строками, она достаточно стабильна при ~ 0,03 с / 11 с и возвращает 1 000 000 строк. (время из MySQL Workbench 6.1; вы также можете использовать CEIL вместо FLOOR во втором операторе выбора в зависимости от ваших предпочтений)
Я хочу указать еще одну возможность ускорения - кэширование . Подумайте, почему вам нужно получить случайные строки. Вероятно, вы хотите разместить на сайте какой-нибудь случайный пост или случайную рекламу. Если вы получаете 100 запросов в секунду, действительно ли нужно, чтобы каждый посетитель получал случайные строки? Обычно вполне нормально кэшировать эти X случайных строк в течение 1 секунды (или даже 10 секунд). Не имеет значения, если 100 уникальных посетителей в одну и ту же секунду получат одинаковые случайные записи, потому что в следующую секунду еще 100 посетителей получат другой набор сообщений.
При использовании этого кэширования вы также можете использовать некоторые из более медленных решений для получения случайных данных, так как они будут выбираться из MySQL только один раз в секунду независимо от ваших запросов / с.
Я улучшил ответ @Riedsio. Это наиболее эффективный запрос, который я могу найти в большой равномерно распределенной таблице с пробелами (проверено на получение 1000 случайных строк из таблицы, в которой> 2,6 Б строк).
(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max :=(SELECT MAX(id)FROMtable))+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)
Позвольте мне распаковать, что происходит.
@max := (SELECT MAX(id) FROM table)
Я рассчитываю и сохраняю макс. Для очень больших таблиц есть небольшие издержки для расчета MAX(id)каждый раз, когда вам нужна строка
SELECT FLOOR(rand() * @max) + 1 as rand)
Получает случайный идентификатор
SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
Это заполняет пробелы. В основном, если вы случайно выберете число в промежутках, он просто выберет следующий идентификатор. Предполагая, что промежутки равномерно распределены, это не должно быть проблемой.
Выполнение объединения поможет вам вписать все в один запрос, чтобы избежать выполнения нескольких запросов. Это также позволяет вам сэкономить на расчетах MAX(id). В зависимости от вашего приложения, это может иметь большое значение или очень мало.
Обратите внимание, что это получает только идентификаторы и получает их в случайном порядке. Если вы хотите сделать что-то более сложное, я рекомендую вам сделать это:
SELECT t.id, t.name -- etc, etcFROMtable t
INNERJOIN((SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max :=(SELECT MAX(id)FROMtable))+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)UNION(SELECT id FROMtableINNERJOIN(SELECT FLOOR(RAND()*@max)+1as rand) r on id > rand LIMIT 1)) x ON x.id = t.id
ORDERBY t.id
Мне нужно 30 случайных записей, так что я должен изменить , LIMIT 1чтобы LIMIT 30везде в запросе
Хассан
@ Hassaan, вы не должны этого менять, LIMIT 1чтобы LIMIT 30получить 30 записей подряд из случайной точки в таблице. Вместо этого у вас должно быть 30 копий (SELECT id FROM ....части в середине.
Ганс Z
Я пытался, но не кажется более эффективным, чем Riedsioответить. Я пытался с 500 попаданиями в секунду на страницу, используя PHP 7.0.22 и MariaDB на centos 7, с Riedsioответом я получил 500+ дополнительных успешных ответов, затем ваш ответ.
Хасан
1
Ответ @Hassaan riedsio дает 1 строку, этот дает вам n строк, а также сокращает накладные расходы ввода / вывода для запросов. Вы можете получить строки быстрее, но с большей нагрузкой на вашу систему.
DROP TEMPORARY TABLEIFEXISTS rands;CREATE TEMPORARY TABLE rands ( rand_id INT );
loop_me: LOOP
IF cnt <1THEN
LEAVE loop_me;ENDIF;INSERTINTO rands
SELECT r1.id
FROM random AS r1 JOIN(SELECT(RAND()*(SELECT MAX(id)FROM random))AS id)AS r2
WHERE r1.id >= r2.id
ORDERBY r1.id ASC
LIMIT 1;SET cnt = cnt -1;END LOOP loop_me;
В этой статье он решает проблему пропусков в идентификаторах, приводящих к не столь случайным результатам, путем ведения таблицы (с использованием триггеров и т. Д. См. Статью); Я решаю проблему, добавив в таблицу еще один столбец, заполненный непрерывными числами, начиная с 1 ( правка: этот столбец добавляется во временную таблицу, созданную подзапросом во время выполнения, не влияет на вашу постоянную таблицу):
DROP TEMPORARY TABLEIFEXISTS rands;CREATE TEMPORARY TABLE rands ( rand_id INT );
loop_me: LOOP
IF cnt <1THEN
LEAVE loop_me;ENDIF;SET@no_gaps_id :=0;INSERTINTO rands
SELECT r1.id
FROM(SELECT id,@no_gaps_id :=@no_gaps_id +1AS no_gaps_id FROM random)AS r1 JOIN(SELECT(RAND()*(SELECT COUNT(*)FROM random))AS id)AS r2
WHERE r1.no_gaps_id >= r2.id
ORDERBY r1.no_gaps_id ASC
LIMIT 1;SET cnt = cnt -1;END LOOP loop_me;
В статье я вижу, что он пошел на все, чтобы оптимизировать код; у меня нет никаких идей, если / насколько мои изменения повлияют на производительность, но работают очень хорошо для меня.
«У меня нет идей, если / насколько мои изменения повлияют на производительность» - довольно много. Для @no_gaps_idиндекса не может быть использован, так что если вы посмотрите на EXPLAINваш запрос, у вас есть Using filesortи Using where(без индекса) для подзапросов, в отличии от исходного запроса.
Фабиан Шменглер,
2
Вот изменитель игры, который может быть полезным для многих;
У меня есть таблица с 200k строк, с последовательными идентификаторами , мне нужно было выбрать N случайных строк, поэтому я решил генерировать случайные значения на основе наибольшего идентификатора в таблице, я создал этот скрипт, чтобы выяснить, какая операция быстрее всего:
logTime();
query("SELECT COUNT(id) FROM tbl");
logTime();
query("SELECT MAX(id) FROM tbl");
logTime();
query("SELECT id FROM tbl ORDER BY id DESC LIMIT 1");
logTime();
Результаты:
Количество: 36.8418693542479мс
Макс: 0.241041183472мс
Заказ: 0.216960906982мс
Основываясь на этих результатах, order desc является самой быстрой операцией для получения максимального идентификатора.
Вот мой ответ на вопрос:
SELECT GROUP_CONCAT(n SEPARATOR ',') g FROM(SELECT FLOOR(RAND()*(SELECT id FROM tbl ORDERBY id DESC LIMIT 1)) n FROM tbl LIMIT 10) a
...SELECT*FROM tbl WHERE id IN($result);
К вашему сведению: чтобы получить 10 случайных строк из таблицы 200k, мне потребовалось 1,78 мс (включая все операции на стороне php)
Я думал о том же решении, скажите, пожалуйста, это быстрее, чем другие методы?
Г.
@ G.Adnane это не быстрее или медленнее, чем принятый ответ, но принятый ответ предполагает равное распределение идентификаторов. Я не могу представить ни одного сценария, где это может быть гарантировано. Это решение находится в O (1), где решение SELECT column FROM table ORDER BY RAND() LIMIT 10находится в O (nlog (n)). Так что да, это быстрое решение, и оно работает для любого распространения идентификаторов.
Адам
нет, потому что в ссылке, размещенной для принятого решения, есть другие методы, я хочу знать, быстрее ли это решение, чем другие, другими способами, мы можем попытаться найти другое, поэтому я спрашиваю, в любом случае, +1 за ваш ответ. Я использовал то же самое
Г.
есть случай, когда вы хотите получить x рядов строк, но смещение переходит в конец таблицы, который возвращает <x строк или только 1 строку. я не видел ваш ответ до того, как опубликовал свой, но я пояснил это здесь stackoverflow.com/a/59981772/10387008
ZOLDIK
@ZOLDIK кажется, что вы выбираете первые 10 строк после смещения x. Я бы сказал, что это не случайное поколение из 10 строк. В моем ответе вы должны выполнить запрос в шаге три 10 раз, то есть каждый получает только одну строку за выполнение и не должен беспокоиться, если смещение находится в конце таблицы.
Адам
1
Если у вас есть только один запрос на чтение
Объедините ответ @redsio с temp-таблицей (600K не так уж много):
DROP TEMPORARY TABLEIFEXISTS tmp_randorder;CREATETABLE tmp_randorder (id int(11)notnull auto_increment primarykey, data_id int(11));INSERTINTO tmp_randorder (data_id)select id from datatable;
А затем возьмите версию @redsios. Ответ:
SELECT dt.*FROM(SELECT(RAND()*(SELECT MAX(id)FROM tmp_randorder))AS id)AS rnd
INNERJOIN tmp_randorder rndo on rndo.id between rnd.id -10and rnd.id +10INNERJOIN datatable AS dt on dt.id = rndo.data_id
ORDERBY abs(rndo.id - rnd.id)
LIMIT 1;
Если стол большой, вы можете просеять первую часть:
INSERTINTO tmp_randorder (data_id)select id from datatable where rand()<0.01;
Если у вас много запросов на чтение
Версия: Вы можете сохранить таблицу tmp_randorderпостоянной, назовите ее datatable_idlist. Повторно создавайте эту таблицу через определенные промежутки времени (день, час), так как она также будет иметь дыры. Если ваш стол становится действительно большим, вы также можете заполнить отверстия
выберите l.data_id как целое из списка данных lidatatatable dt для dt.id = l.data_id, где dt.id равен нулю;
Версия: Дайте вашему набору данных столбец random_sortorder либо непосредственно в datatable, либо в постоянной дополнительной таблице datatable_sortorder. Индексируйте этот столбец. Создайте случайное значение в вашем приложении (я назову это $rand).
select l.*from datatable l
orderby abs(random_sortorder -$rand)desc
limit 1;
Это решение различает «крайние строки» с самым высоким и самым низким random_sortorder, поэтому переставляйте их с интервалами (один раз в день).
Другим простым решением будет ранжирование строк и выборка одной из них случайным образом, и с этим решением вам не нужно будет иметь столбец на основе 'Id' в таблице.
SELECT d.*FROM(SELECT t.*,@rownum :=@rownum +1AS rank
FROM mytable AS t,(SELECT@rownum :=0)AS r,(SELECT@cnt :=(SELECT RAND()*(SELECT COUNT(*)FROM mytable)))AS n
) d WHERE rank >=@cnt LIMIT 10;
Вы можете изменить предельное значение в соответствии с вашими потребностями, чтобы получить доступ к столько строк, сколько вы хотите, но в большинстве случаев это будут последовательные значения.
Однако, если вам не нужны последовательные случайные значения, вы можете выбрать большую выборку и выбрать ее случайным образом. что-то вроде ...
SELECT*FROM(SELECT d.*FROM(SELECT c.*,@rownum :=@rownum +1AS rank
FROM buildbrain.`commits`AS c,(SELECT@rownum :=0)AS r,(SELECT@cnt :=(SELECT RAND()*(SELECT COUNT(*)FROM buildbrain.`commits`)))AS rnd
) d
WHERE rank >=@cnt LIMIT 10000) t ORDERBY RAND() LIMIT 10;
Один способ, который я нахожу довольно хорошим, если есть автоматически сгенерированный идентификатор, это использовать оператор по модулю "%". Например, если вам нужно 10 000 случайных записей из 70 000, вы можете упростить это, сказав, что вам нужна 1 из каждых 7 строк. Это может быть упрощено в этом запросе:
SELECT*FROMtableWHERE
id %
FLOOR((SELECT count(1)FROMtable)/10000)=0;
Если результат деления целевых строк на общее количество не является целым числом, у вас будет несколько дополнительных строк, чем то, что вы просили, поэтому вы должны добавить предложение LIMIT, чтобы помочь вам обрезать результирующий набор следующим образом:
SELECT*FROMtableWHERE
id %
FLOOR((SELECT count(1)FROMtable)/10000)=0
LIMIT 10000;
Это требует полного сканирования, но это быстрее, чем ORDER BY RAND, и, на мой взгляд, проще для понимания, чем другие опции, упомянутые в этой теме. Также, если система, которая записывает в БД, создает наборы строк в пакетах, вы можете не получить такой случайный результат, как ожидали.
Теперь, когда я так думаю, если вам нужно каждый раз вызывать случайные строки, это бесполезно. Я думал только о необходимости получить случайные строки из набора, чтобы провести некоторое исследование. Я все еще думаю, что по модулю хорошая вещь, чтобы помочь в другом случае. Вы можете использовать модуль по модулю в качестве фильтра первого прохода, чтобы снизить стоимость операции ORDER BY RAND.
Николас Коэн
1
Если вы хотите одну случайную запись (независимо от того, есть ли пробелы между идентификаторами):
Я просмотрел все ответы, и я не думаю, что кто-то вообще упоминает эту возможность, и я не уверен, почему.
Если вам нужна предельная простота и скорость при минимальных затратах, то, мне кажется, имеет смысл хранить случайное число для каждой строки в БД. Просто создайте дополнительный столбец random_numberи установите для него значение по умолчанию RAND(). Создайте индекс для этого столбца.
Затем, когда вы хотите извлечь строку, сгенерируйте случайное число в вашем коде (PHP, Perl и т. Д.) И сравните его со столбцом.
SELECT FROM tbl WHERE random_number >= :random LIMIT 1
Я предполагаю, что хотя это очень аккуратно для одной строки, для десяти строк, таких как ОП, вас попросят вызвать его десять раз (или придумать хитрый твик, который сразу ускользает от меня)
На самом деле это очень хороший и эффективный подход. Единственным недостатком является тот факт, что вы обменяли пространство на скорость, что, на мой взгляд, кажется справедливой сделкой.
Точукву Нкемдилим
Спасибо. У меня был сценарий, когда в основной таблице, из которой я хотел получить случайную строку, было 5 миллионов строк и довольно много объединений, и после попытки большинства подходов в этом вопросе это был клудж, на котором я остановился. Одна дополнительная колонка была для меня очень выгодным компромиссом.
Codemonkey
0
Следующее должно быть быстрым, беспристрастным и независимым от столбца id. Однако это не гарантирует, что количество возвращаемых строк будет соответствовать количеству запрошенных строк.
SELECT*FROM t
WHERE RAND()<(SELECT10/ COUNT(*)FROM t)
Объяснение: если вы хотите, чтобы 10 строк из 100, то каждая строка имела 1/10 вероятности получения SELECT, чего можно достичь WHERE RAND() < 0.1. Этот подход не гарантирует 10 строк; но если запрос выполняется достаточно раз, среднее число строк на выполнение будет около 10, и каждая строка в таблице будет выбрана равномерно.
PREPARE stm from'select * from table where available=true limit 10 offset ?';SET@total =(select count(*)fromtablewhere available=true);SET@_offset = FLOOR(RAND()*@total);EXECUTE stm using@_offset;
Протестировано на 600 000 строк (700 МБ). Выполнение запроса таблицы заняло ~ 0,016 с жесткого диска.
--EDIT--
Смещение может принимать значение, близкое к концу таблицы, что приведет к тому, что оператор select вернет меньше строк (или, возможно, только 1 строка), чтобы избежать этого, мы можем проверить еще offsetраз после объявления этого, например, так
Черт возьми, это один из худших способов получить случайные строки из таблицы. Это полное сканирование таблицы + сортировка файлов + таблица tmp = плохая производительность.
Мэтт
1
Помимо производительности, это также далеко не совершенно случайно; вы упорядочиваете по произведению идентификатора и случайного числа, а не просто по случайному числу, что означает, что строки с более низкими идентификаторами будут смещены в сторону более раннего появления в наборе результатов.
Ответы:
Отличный пост, обрабатывающий несколько случаев, от простых до пропусков, до неоднородных с пропусками.
http://jan.kneschke.de/projects/mysql/order-by-rand/
Для наиболее общего случая вот как вы это делаете:
Это предполагает, что распределение идентификаторов одинаково, и что в списке идентификаторов могут быть пробелы. Смотрите статью для более продвинутых примеров
источник
mysqli_fetch_assoc($result)
? Или эти 10 результатов не обязательно различимы?Не эффективное решение, но работает
источник
ORDER BY RAND()
относительно медленноSELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10
занимает 0,0010, без LIMIT 10 - 0,0012 (в этой таблице 3500 слов).Простой запрос, который имеет отличную производительность и работает с пробелами :
Этот запрос на 200K таблице занимает 0.08s и нормальную версию (SELECT * FROM TBL ORDER BY RAND () LIMIT 10) принимает 0.35s на моей машине.
Это быстро, потому что на этапе сортировки используется только индексированный столбец идентификаторов. Вы можете увидеть это поведение в объяснении:
ВЫБЕРИТЕ * ОТ ТАБЛИЧНОГО ЗАКАЗА ПО RAND () LIMIT 10:
ВЫБЕРИТЕ * ОТ tbl КАК t1 ПРИСОЕДИНЯЙТЕСЬ (ВЫБЕРИТЕ идентификатор ОТ tbl ЗАКАЗАТЬ ПО RAND () LIMIT 10) при t2 ON t1.id = t2.id
Взвешенная версия : https://stackoverflow.com/a/41577458/893432
источник
Я получаю быстрые запросы (около 0,5 секунд) с медленным процессором , выбирая 10 случайных строк в регистрах 400 КБ базы данных MySQL без кэширования размером 2 ГБ. Смотрите здесь мой код: Быстрый выбор случайных строк в MySQL
источник
ORDER BY RAND()
FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';
чтобы увидеть это.ORDER BY RAND()
заключается в том, что он сортирует только идентификаторы (не полные строки), поэтому временная таблица меньше, но все равно должна сортировать их все.Это очень простой и однострочный запрос.
источник
order by rand()
очень медленно, если стол большойИз книги:
Выберите случайную строку, используя смещение
Еще один метод, который позволяет избежать проблем, обнаруженных в предыдущих альтернативах, заключается в подсчете строк в наборе данных и возврате случайного числа между 0 и счетчиком. Затем используйте это число в качестве смещения при запросе набора данных
Используйте это решение, если вы не можете использовать смежные значения ключей, и вам нужно убедиться, что у каждой строки есть равный шанс выбора.
источник
SELECT count(*)
становится медленным.Как выбрать случайные строки из таблицы:
Отсюда: выберите случайные строки в MySQL
Быстрое улучшение по сравнению с «сканированием таблицы» заключается в использовании индекса для выбора случайных идентификаторов.
источник
PRIMARY KEY
).Хорошо, если у вас нет пробелов в ваших ключах, и они все числовые, вы можете вычислить случайные числа и выбрать эти строки. но это, вероятно, не так.
Таким образом, одним из решений будет следующее:
это в основном гарантирует, что вы получите случайное число в диапазоне ваших ключей, а затем выберете следующий лучший, который больше. Вы должны сделать это 10 раз.
однако это не случайно, потому что ваши ключи, скорее всего, не будут распределяться равномерно.
Это действительно большая проблема, и ее нелегко решить, выполнив все требования, MySQL rand () - лучшее, что вы можете получить, если вам действительно нужно 10 случайных строк.
Однако есть другое решение, которое быстро, но также имеет компромисс, когда дело доходит до случайности, но может подойти вам лучше. Прочитайте об этом здесь: Как я могу оптимизировать функцию ORDER BY RAND () в MySQL?
Вопрос в том, насколько случайным он вам нужен.
Можете ли вы объяснить немного больше, чтобы я мог дать вам хорошее решение.
Например, у компании, с которой я работал, было решение, в котором они нуждались в абсолютной случайности очень быстро. Они закончили с предварительным заполнением базы данных случайными значениями, которые были выбраны по убыванию и впоследствии снова установлены на разные случайные значения.
Если вы вряд ли когда-либо обновите, вы также можете заполнить инкрементный идентификатор, чтобы у вас не было пробелов и вы могли просто вычислить случайные ключи перед выбором ... Это зависит от варианта использования!
источник
Id
и все ваши случайные запросы вернут вам этотId
.FLOOR(RAND()*MAX(id))
склонен к возвращению больших идентификаторов.Мне нужен был запрос, чтобы вернуть большое количество случайных строк из довольно большой таблицы. Это то, что я придумал. Сначала получите максимальный идентификатор записи:
Затем подставьте это значение в:
Где max - максимальный идентификатор записи в таблице, а n - количество строк, которые вы хотите в вашем наборе результатов. Предполагается, что в идентификаторах записей нет пробелов, хотя я сомневаюсь, что это повлияет на результат, если они были (хотя я и не пробовал). Я также создал эту хранимую процедуру, чтобы быть более общей; передайте имя таблицы и количество возвращаемых строк. Я использую MySQL 5.5.38 в Windows 2008, 32 ГБ, двойной EHz50 с частотой 3 ГГц и в таблице с 17 361 264 строками, она достаточно стабильна при ~ 0,03 с / 11 с и возвращает 1 000 000 строк. (время из MySQL Workbench 6.1; вы также можете использовать CEIL вместо FLOOR во втором операторе выбора в зависимости от ваших предпочтений)
тогда
источник
Все лучшие ответы уже опубликованы (в основном те, на которые ссылается ссылка http://jan.kneschke.de/projects/mysql/order-by-rand/ ).
Я хочу указать еще одну возможность ускорения - кэширование . Подумайте, почему вам нужно получить случайные строки. Вероятно, вы хотите разместить на сайте какой-нибудь случайный пост или случайную рекламу. Если вы получаете 100 запросов в секунду, действительно ли нужно, чтобы каждый посетитель получал случайные строки? Обычно вполне нормально кэшировать эти X случайных строк в течение 1 секунды (или даже 10 секунд). Не имеет значения, если 100 уникальных посетителей в одну и ту же секунду получат одинаковые случайные записи, потому что в следующую секунду еще 100 посетителей получат другой набор сообщений.
При использовании этого кэширования вы также можете использовать некоторые из более медленных решений для получения случайных данных, так как они будут выбираться из MySQL только один раз в секунду независимо от ваших запросов / с.
источник
Я улучшил ответ @Riedsio. Это наиболее эффективный запрос, который я могу найти в большой равномерно распределенной таблице с пробелами (проверено на получение 1000 случайных строк из таблицы, в которой> 2,6 Б строк).
Позвольте мне распаковать, что происходит.
@max := (SELECT MAX(id) FROM table)
MAX(id)
каждый раз, когда вам нужна строкаSELECT FLOOR(rand() * @max) + 1 as rand)
SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
Выполнение объединения поможет вам вписать все в один запрос, чтобы избежать выполнения нескольких запросов. Это также позволяет вам сэкономить на расчетах
MAX(id)
. В зависимости от вашего приложения, это может иметь большое значение или очень мало.Обратите внимание, что это получает только идентификаторы и получает их в случайном порядке. Если вы хотите сделать что-то более сложное, я рекомендую вам сделать это:
источник
LIMIT 1
чтобыLIMIT 30
везде в запросеLIMIT 1
чтобыLIMIT 30
получить 30 записей подряд из случайной точки в таблице. Вместо этого у вас должно быть 30 копий(SELECT id FROM ....
части в середине.Riedsio
ответить. Я пытался с 500 попаданиями в секунду на страницу, используя PHP 7.0.22 и MariaDB на centos 7, сRiedsio
ответом я получил 500+ дополнительных успешных ответов, затем ваш ответ.Я использовал этот http://jan.kneschke.de/projects/mysql/order-by-rand/, опубликованный Riedsio (я использовал случай хранимой процедуры, которая возвращает одно или несколько случайных значений):
В этой статье он решает проблему пропусков в идентификаторах, приводящих к не столь случайным результатам, путем ведения таблицы (с использованием триггеров и т. Д. См. Статью); Я решаю проблему, добавив в таблицу еще один столбец, заполненный непрерывными числами, начиная с 1 ( правка: этот столбец добавляется во временную таблицу, созданную подзапросом во время выполнения, не влияет на вашу постоянную таблицу):
В статье я вижу, что он пошел на все, чтобы оптимизировать код; у меня нет никаких идей, если / насколько мои изменения повлияют на производительность, но работают очень хорошо для меня.
источник
@no_gaps_id
индекса не может быть использован, так что если вы посмотрите наEXPLAIN
ваш запрос, у вас естьUsing filesort
иUsing where
(без индекса) для подзапросов, в отличии от исходного запроса.Вот изменитель игры, который может быть полезным для многих;
У меня есть таблица с 200k строк, с последовательными идентификаторами , мне нужно было выбрать N случайных строк, поэтому я решил генерировать случайные значения на основе наибольшего идентификатора в таблице, я создал этот скрипт, чтобы выяснить, какая операция быстрее всего:
Результаты:
36.8418693542479
мс0.241041183472
мс0.216960906982
мсОсновываясь на этих результатах, order desc является самой быстрой операцией для получения максимального идентификатора.
Вот мой ответ на вопрос:
К вашему сведению: чтобы получить 10 случайных строк из таблицы 200k, мне потребовалось 1,78 мс (включая все операции на стороне php)
источник
LIMIT
немного увеличить - вы можете получить дубликаты.Это супер быстро и на 100% случайно, даже если у вас есть пробелы.
x
доступных вам строкSELECT COUNT(*) as rows FROM TABLE
a_1,a_2,...,a_10
от 0 доx
SELECT * FROM TABLE LIMIT 1 offset a_i
для i = 1, ..., 10Я нашел этот взлом в книге « Антипаттерны SQL» от Билла Карвина .
источник
SELECT column FROM table ORDER BY RAND() LIMIT 10
находится в O (nlog (n)). Так что да, это быстрое решение, и оно работает для любого распространения идентификаторов.x
. Я бы сказал, что это не случайное поколение из 10 строк. В моем ответе вы должны выполнить запрос в шаге три 10 раз, то есть каждый получает только одну строку за выполнение и не должен беспокоиться, если смещение находится в конце таблицы.Если у вас есть только один запрос на чтение
Объедините ответ @redsio с temp-таблицей (600K не так уж много):
А затем возьмите версию @redsios. Ответ:
Если стол большой, вы можете просеять первую часть:
Если у вас много запросов на чтение
Версия: Вы можете сохранить таблицу
tmp_randorder
постоянной, назовите ее datatable_idlist. Повторно создавайте эту таблицу через определенные промежутки времени (день, час), так как она также будет иметь дыры. Если ваш стол становится действительно большим, вы также можете заполнить отверстиявыберите l.data_id как целое из списка данных lidatatatable dt для dt.id = l.data_id, где dt.id равен нулю;
Версия: Дайте вашему набору данных столбец random_sortorder либо непосредственно в datatable, либо в постоянной дополнительной таблице
datatable_sortorder
. Индексируйте этот столбец. Создайте случайное значение в вашем приложении (я назову это$rand
).Это решение различает «крайние строки» с самым высоким и самым низким random_sortorder, поэтому переставляйте их с интервалами (один раз в день).
источник
Другим простым решением будет ранжирование строк и выборка одной из них случайным образом, и с этим решением вам не нужно будет иметь столбец на основе 'Id' в таблице.
Вы можете изменить предельное значение в соответствии с вашими потребностями, чтобы получить доступ к столько строк, сколько вы хотите, но в большинстве случаев это будут последовательные значения.
Однако, если вам не нужны последовательные случайные значения, вы можете выбрать большую выборку и выбрать ее случайным образом. что-то вроде ...
источник
Один способ, который я нахожу довольно хорошим, если есть автоматически сгенерированный идентификатор, это использовать оператор по модулю "%". Например, если вам нужно 10 000 случайных записей из 70 000, вы можете упростить это, сказав, что вам нужна 1 из каждых 7 строк. Это может быть упрощено в этом запросе:
Если результат деления целевых строк на общее количество не является целым числом, у вас будет несколько дополнительных строк, чем то, что вы просили, поэтому вы должны добавить предложение LIMIT, чтобы помочь вам обрезать результирующий набор следующим образом:
Это требует полного сканирования, но это быстрее, чем ORDER BY RAND, и, на мой взгляд, проще для понимания, чем другие опции, упомянутые в этой теме. Также, если система, которая записывает в БД, создает наборы строк в пакетах, вы можете не получить такой случайный результат, как ожидали.
источник
Если вы хотите одну случайную запись (независимо от того, есть ли пробелы между идентификаторами):
Источник: https://www.warpconduit.net/2011/03/23/selecting-a-random-record-using-mysql-benchmark-results/#comment-1266
источник
Я просмотрел все ответы, и я не думаю, что кто-то вообще упоминает эту возможность, и я не уверен, почему.
Если вам нужна предельная простота и скорость при минимальных затратах, то, мне кажется, имеет смысл хранить случайное число для каждой строки в БД. Просто создайте дополнительный столбец
random_number
и установите для него значение по умолчаниюRAND()
. Создайте индекс для этого столбца.Затем, когда вы хотите извлечь строку, сгенерируйте случайное число в вашем коде (PHP, Perl и т. Д.) И сравните его со столбцом.
SELECT FROM tbl WHERE random_number >= :random LIMIT 1
Я предполагаю, что хотя это очень аккуратно для одной строки, для десяти строк, таких как ОП, вас попросят вызвать его десять раз (или придумать хитрый твик, который сразу ускользает от меня)
источник
Следующее должно быть быстрым, беспристрастным и независимым от столбца id. Однако это не гарантирует, что количество возвращаемых строк будет соответствовать количеству запрошенных строк.
Объяснение: если вы хотите, чтобы 10 строк из 100, то каждая строка имела 1/10 вероятности получения SELECT, чего можно достичь
WHERE RAND() < 0.1
. Этот подход не гарантирует 10 строк; но если запрос выполняется достаточно раз, среднее число строк на выполнение будет около 10, и каждая строка в таблице будет выбрана равномерно.источник
Вы можете легко использовать случайное смещение с лимитом
Вы также можете применить предложение where как
Протестировано на 600 000 строк (700 МБ). Выполнение запроса таблицы заняло ~ 0,016 с жесткого диска.
--EDIT--
Смещение может принимать значение, близкое к концу таблицы, что приведет к тому, что оператор select вернет меньше строк (или, возможно, только 1 строка), чтобы избежать этого, мы можем проверить еще
offset
раз после объявления этого, например, такисточник
Я использую этот запрос:
время запроса: 0.016с
источник
Вот как я это делаю:
Мне нравится это, потому что не требует других таблиц, это просто написать, и это очень быстро выполнить.
источник
Используйте приведенный ниже простой запрос, чтобы получить случайные данные из таблицы.
источник
Я думаю, это самый лучший способ ..
источник