Самый быстрый способ рандомизации результата с большим набором данных в MySQL - PullRequest
0 голосов
/ 27 августа 2018

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

Попытка:

1) выбрать * из порядка таблицы с помощью rand () limit 1

2) выберите * из таблицы, где идентификатор в (выберите идентификатор из порядка таблицы по rand () limit 1)

2 быстрее, чем 1, но все еще слишком медленный для таблицы с большими строками

Обновление: Запрос используется в приложении реального времени.Вставить, выбрать и обновить примерно 10 / сек.Поэтому кэширование не будет идеальным решением.Строк, необходимых для этого конкретного случая, равно 1. Но нужно также найти общее решение, когда запрос быстрый и количество строк требуется> 1

Ответы [ 4 ]

0 голосов
/ 27 августа 2018

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

  1. генерация всех этих чисел
  2. нахождение минимального числа

и последующий доступ к записи курса.

Если вам нужно более одной строки, СУБД может отсортировать все записи и затем вернуть n записей, но, надеюсь, она предпочла бы применить некоторую операцию частичной сортировки, когда она обнаруживает только n минимальных чисел.Во всяком случае, какое-то задание.

Полного пути, как мне кажется, не существует.Если вам нужен произвольный доступ, то это путь.

Если бы вы были готовы жить с менее случайным результатом, я бы посоветовал сделать корзины с идентификаторами.Представьте, что ID сегментов 000000-0999999, 100000-1999999, ... Затем случайным образом выберите один сегмент, и из этого выберите случайные строки.Ну, по общему признанию, это не выглядит очень случайным, и вы получите либо старые, либо только новые записи с такими группами;но он иллюстрирует технику.

Вместо создания сегментов по значению, вы создадите их с помощью функции по модулю.id % 1000 даст вам 1000 ведер.Первый с идентификаторами xxx000, второй с идентификаторами xxx001.Это позволит решить проблему с новыми / старыми записями и сбалансировать ведра.Поскольку идентификаторы - это просто техническая вещь, совершенно неважно, что нарисованные идентификаторы выглядят очень похожими.И даже если это вас беспокоит, тогда не делайте 1000 бакетов, а говорите 997.

Теперь создайте вычисляемый столбец:

alter table mytable add column bucket int generated always as (id % 997) stored;

Добавьте индекс:

create index idx on mytable(bucket);

И запрос данных:

select *
from mytable
where bucket = floor(rand() * 998)
order by rand()
limit 10;

Только около 0,1% таблицы попадает в сортировку здесь.Так что это должно быть довольно быстро.Но я предполагаю, что платит только с очень большой таблицей и большим количеством сегментов.

Недостатки метода:

  • Может случиться, что вы не получите столько строккак вы хотите, и вам придется запросить снова.
  • Вы должны выбрать номер по модулю с умом.Если в таблице всего две тысячи записей, вы, конечно, не сделаете 1000 сегментов, но, возможно, 100 и никогда не будете требовать больше, скажем, десяти строк за раз.
  • Если таблица увеличивается и растетодноразово выбранное число больше не может быть оптимальным, и вы, возможно, захотите изменить его.

Ссылка на реекстер: http://rextester.com/VDPIU7354

ОБНОВЛЕНИЕ: Это только что осенилона мне, что сегменты будут действительно случайными, если сгенерированный столбец будет основан не на модуле по идентификатору, а на RAND значении:

alter table mytable add column bucket int generated always as (floor(rand() * 1000)) stored;

, но MySQL выдает ошибку "Выражениесгенерированного столбца 'bucket' содержит запрещенную функцию ".Это, кажется, не имеет смысла, так как недетерминированная функция должна быть в порядке с опцией STORED, но по крайней мере в версии 5.7.12 это не работает.Может быть, в более поздней версии?

0 голосов
/ 27 августа 2018

Создайте случайный набор идентификаторов перед выполнением запроса (вы также можете очень быстро получить MAX (id), если вам это нужно).Затем выполните запрос как id IN (your, list).При этом индекс будет использоваться только для просмотра запрошенных вами идентификаторов, поэтому он будет очень быстрым.

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

0 голосов
/ 27 августа 2018

Если вы можете запустить два запроса в одном и том же «вызове», вы можете сделать что-то вроде этого, к сожалению, это подразумевает, что в вашей базе данных нет удаленных записей ... если они, где какой-то запрос не будет ничего возвращать.

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

SET @randy = CAST(rand()*(SELECT MAX(id) FROM yourtable) as UNSIGNED);

SELECT *
FROM yourtable
WHERE id = @randy;

Другое решение, появившееся в результате небольшого изменения ответа на этот вопрос и из вашего собственного решения: Использование переменных в качестве OFFSET в параметрах SELECT внутри хранимых функций mysql

SET @randy = CAST(rand()*(SELECT MAX(id) FROM yourtable) as UNSIGNED);
SET @q1 = CONCAT('SELECT * FROM yourtable LIMIT 1 OFFSET ', @randy);
PREPARE stmt1 FROM @q1;
EXECUTE stmt1;
0 голосов
/ 27 августа 2018

Самый быстрый способ - использовать подготовленный оператор в mysql и ограничить

select @offset:=floor(rand()*total_rows_in_table);
PREPARE STMT FROM 'select id from table limit ?,1';
EXECUTE STMT USING @offset; 

total_rows_in_table = общее количество строк в таблице.

Это намного быстрее по сравнению с двумя выше.

Ограничение: извлечение более 1 строки не является действительно случайным.

...