#mysql #design-patterns #orm #random #query-optimization
#mysql #шаблоны проектирования #orm #Случайный #оптимизация запросов
Вопрос:
В нескольких проектах, над которыми я работал, я столкнулся с необходимостью извлекать случайные строки из больших (> 1 МЛН строк) таблиц. С таблицами такого размера ORDER BY rand() LIMIT 1
это не вариант, поскольку это быстро поставит базу данных на колени.
Обычным решением было сгенерировать случайное число между MIN(id)
и MAX(id)
и выбрать эту строку напрямую. Однако, если в последовательности идентификаторов есть большие пробелы, это потребует либо большого количества повторных запусков, либо использования WHERE id >= :myrandomnumber
, что приведет к тому, что строки, которые следуют за большими пробелами, получат значительно больше обращений, чем в среднем.
Я думал решить эту проблему, создав новый индексированный столбец исключительно для целей рандомизации, скажем id2
. Этот столбец всегда будет представлять собой последовательность без пробелов между 1 и количеством строк в таблице.
Вопрос: каков был бы наилучший способ сохранить эту последовательность без пропусков?
Первое решение, которое приходит на ум, — это создание вспомогательной таблицы, recycled_ids
которая будет содержать столбцы tablename
и id2
. Всякий раз, когда строка удаляется из tablename
, id2
эта строка вставляется в recycled_ids
. Когда вставляются новые строки, id2
либо выбирается из recycled_ids
, либо, если ни одна из них не доступна, создается новая. Есть ли более простой способ?
Дополнительные вопросы:Существуют ли ORM или фреймворки, которые уже делают это или иным образом обеспечивают эффективный случайный выбор строк? Это существующий шаблон, есть ли у него имя?
Обновление: Я написал для этого быстрый тест и сравнил его с таблицей со 125 000 строками и 30 000 пробелами между ними. Результаты довольно многообещающие:
Fetch a random row 100 times using id2: 0.0234689712524 seconds
Fetch a random row 100 times using ORDER BY rand() LIMIT 1: 54.992347002 seconds
При вставке тестовых данных я удалил одну случайную строку на каждые пять вставленных строк. Последовательность остается без пробелов все время.
for($i=1; $i<=$amount; $i ) {
insert_row();
if($i % 5 == 0)
delete_random_row();
}
Повторный запуск этого цикла с $amount = 10000
занимает 9 секунд на моем vserver низкого уровня. Это 0,009 секунды на строку, и это включает удаление случайной строки каждые пять итераций. По мере роста таблицы он становится медленнее, но выборка случайной строки этого не делает.
Мои первоначальные вопросы все еще актуальны.
Ответ №1:
Вот как я бы это сделал —
- Выберите МАКСИМАЛЬНОЕ значение (id) из вашей таблицы
- В PHP (или любом другом языке, который вы используете) сгенерируйте случайное целое число от 1 до MAX (id)
SELECT * FROM table WHERE id >= $random ORDER BY id ASC LIMIT 1
- Если 3 ничего не возвращает,
SELECT * FROM table WHERE id < $random ORDER BY id DESC LIMIT 1
Позволяет избежать выполнения любых запросов, которые были бы ужасно медленными. Это также позволяет избежать дополнительного столбца, который, сохраняя пробелы, был бы действительно неприятной работой!
Комментарии:
1. Спасибо за ответ, но это не подойдет, если есть пробелы, а я хочу равномерное распределение случайности. Представьте себе преувеличенный пример таблицы с тремя строками с идентификаторами
1, 2, 1000000
. Вероятность появления первых двух строк была бы одна на миллион.2. Вы могли бы дополнительно рандомизировать его, манипулируя пределом, основанным на диапазоне и случайно сгенерированном значении.
3. Какого рода манипуляции вы имеете в виду? Я не могу придумать никакого другого способа получить шансы, даже близкие к 33% для каждой, чем
ORDER BY rand()
или дополнительный столбец, который я описал.
Ответ №2:
Я бы сказал, что рейтинг помогает.
SET @rank:= 1;
SELECT * FROM
(
SELECT @rank:= @rank 1 as rank, * FROM table1
) s
WHERE s.rank = $random;
Комментарии:
1. Поправьте меня, если я ошибаюсь, но это выглядит как абсолютно убийственный запрос для таблицы с миллионом строк.
2. @Kaivosukeltaja, ты прав #:-. Если у вас есть какая-либо яркая идея о том, как сделать это быстро для 1 миллиона строк, я был бы рад ее услышать. Конечно, вы можете запустить ранжирование как
update
, используя дополнительный столбец в таблице, тогда вам придется отключить сервер только один раз.3. У меня есть подозрение, что вторичные идентификаторы могли бы сделать это довольно быстро. Я думаю, мне придется протестировать его и посмотреть, как это работает.