Я хотел бы оптимизировать свои запросы, чтобы я изучил mysql-slow.log
.
Большинство моих медленных запросов содержит ORDER BY RAND()
. Я не могу найти реального решения этой проблемы. Theres является возможным решением в MySQLPerformanceBlog , но я не думаю , что этого достаточно. В плохо оптимизированных (или часто обновляемых, управляемых пользователем) таблицах это не работает, или мне нужно выполнить два или более запроса, прежде чем я смогу выбрать PHP
сгенерированную мной случайную строку.
Есть ли решение этой проблемы?
Пустой пример:
SELECT accomodation.ac_id,
accomodation.ac_status,
accomodation.ac_name,
accomodation.ac_status,
accomodation.ac_images
FROM accomodation, accomodation_category
WHERE accomodation.ac_status != 'draft'
AND accomodation.ac_category = accomodation_category.acat_id
AND accomodation_category.acat_slug != 'vendeglatohely'
AND ac_images != 'b:0;'
ORDER BY
RAND()
LIMIT 1
mysql
random
performance
фабрик
источник
источник
Ответы:
Попробуй это:
SELECT * FROM ( SELECT @cnt := COUNT(*) + 1, @lim := 10 FROM t_random ) vars STRAIGHT_JOIN ( SELECT r.*, @lim := @lim - 1 FROM t_random r WHERE (@cnt := @cnt - 1) AND RAND(20090301) < @lim / @cnt ) i
Это особенно эффективно на
MyISAM
(так какCOUNT(*)
мгновенно), но даже вInnoDB
это10
время более эффективно , чемORDER BY RAND()
.Основная идея здесь в том, что мы не сортируем, а вместо этого сохраняем две переменные и вычисляем
running probability
строку, которая будет выбрана на текущем шаге.См. Эту статью в моем блоге для более подробной информации:
Обновить:
Если вам нужно выбрать только одну случайную запись, попробуйте следующее:
SELECT aco.* FROM ( SELECT minid + FLOOR((maxid - minid) * RAND()) AS randid FROM ( SELECT MAX(ac_id) AS maxid, MIN(ac_id) AS minid FROM accomodation ) q ) q2 JOIN accomodation aco ON aco.ac_id = COALESCE ( ( SELECT accomodation.ac_id FROM accomodation WHERE ac_id > randid AND ac_status != 'draft' AND ac_images != 'b:0;' AND NOT EXISTS ( SELECT NULL FROM accomodation_category WHERE acat_id = ac_category AND acat_slug = 'vendeglatohely' ) ORDER BY ac_id LIMIT 1 ), ( SELECT accomodation.ac_id FROM accomodation WHERE ac_status != 'draft' AND ac_images != 'b:0;' AND NOT EXISTS ( SELECT NULL FROM accomodation_category WHERE acat_id = ac_category AND acat_slug = 'vendeglatohely' ) ORDER BY ac_id LIMIT 1 ) )
Это предполагает, что ваши
ac_id
файлы распределены более или менее равномерно.источник
@fabrik
: попробуй сейчас. Было бы очень полезно, если бы вы разместили скрипты таблиц, чтобы я мог проверить их перед публикацией.Это зависит от того, насколько случайным вы должны быть. Решение, которое вы связали, работает очень хорошо, IMO. Если у вас нет больших пробелов в поле идентификатора, оно все равно довольно случайное.
Однако вы должны иметь возможность сделать это в одном запросе, используя это (для выбора одного значения):
SELECT [fields] FROM [table] WHERE id >= FLOOR(RAND()*MAX(id)) LIMIT 1
Другие решения:
random
в таблицу постоянное поле с плавающей запятой и заполните его случайными числами. Затем вы можете сгенерировать случайное число в PHP и выполнить"SELECT ... WHERE rnd > $random"
источник
SELECT [fields] FROM [table] WHERE id >= FLOOR(RAND()*(SELECT MAX(id) FROM [table])) LIMIT 1
но, похоже, это не работает должным образом, поскольку он никогда не возвращает последнюю записьSELECT [fields] FROM [table] WHERE id >= FLOOR(1 + RAND()*(SELECT MAX(id) FROM [table])) LIMIT 1
Кажется, это помогает мнеВот как бы я это сделал:
SET @r := (SELECT ROUND(RAND() * (SELECT COUNT(*) FROM accomodation a JOIN accomodation_category c ON (a.ac_category = c.acat_id) WHERE a.ac_status != 'draft' AND c.acat_slug != 'vendeglatohely' AND a.ac_images != 'b:0;'; SET @sql := CONCAT(' SELECT a.ac_id, a.ac_status, a.ac_name, a.ac_status, a.ac_images FROM accomodation a JOIN accomodation_category c ON (a.ac_category = c.acat_id) WHERE a.ac_status != ''draft'' AND c.acat_slug != ''vendeglatohely'' AND a.ac_images != ''b:0;'' LIMIT ', @r, ', 1'); PREPARE stmt1 FROM @sql; EXECUTE stmt1;
источник
OFFSET
(то@r
есть для чего) не предотвращает сканирования - вплоть до полного сканирования таблицы.(Да, меня накажут за то, что здесь недостаточно мяса, но разве ты не можешь быть веганом на один день?)
Случай: последовательный AUTO_INCREMENT без пробелов, возвращается 1 строка.
Случай: последовательный AUTO_INCREMENT без пробелов, 10 строк.
Случай: AUTO_INCREMENT с пробелами, возвращается 1 строка.
Случай: дополнительный столбец FLOAT для рандомизации.
Случай: столбец UUID или MD5.
Эти 5 случаев можно сделать очень эффективными для больших столов. Видеть моем блоге .
источник
Это даст вам один подзапрос, который будет использовать индекс для получения случайного идентификатора, тогда другой запрос запустит вашу объединенную таблицу.
SELECT accomodation.ac_id, accomodation.ac_status, accomodation.ac_name, accomodation.ac_status, accomodation.ac_images FROM accomodation, accomodation_category WHERE accomodation.ac_status != 'draft' AND accomodation.ac_category = accomodation_category.acat_id AND accomodation_category.acat_slug != 'vendeglatohely' AND ac_images != 'b:0;' AND accomodation.ac_id IS IN ( SELECT accomodation.ac_id FROM accomodation ORDER BY RAND() LIMIT 1 )
источник
Решение для вашего фиктивного примера:
SELECT accomodation.ac_id, accomodation.ac_status, accomodation.ac_name, accomodation.ac_status, accomodation.ac_images FROM accomodation, JOIN accomodation_category ON accomodation.ac_category = accomodation_category.acat_id JOIN ( SELECT CEIL(RAND()*(SELECT MAX(ac_id) FROM accomodation)) AS ac_id ) AS Choices USING (ac_id) WHERE accomodation.ac_id >= Choices.ac_id AND accomodation.ac_status != 'draft' AND accomodation_category.acat_slug != 'vendeglatohely' AND ac_images != 'b:0;' LIMIT 1
Чтобы узнать больше об альтернативах
ORDER BY RAND()
, вы должны прочитать эту статью .источник
Я оптимизирую множество существующих запросов в своем проекте. Решение Quassnoi помогло мне значительно ускорить запросы! Однако мне сложно включить указанное решение во все запросы, особенно для сложных запросов, включающих множество подзапросов в нескольких больших таблицах.
Поэтому я использую менее оптимизированное решение. По сути, это работает так же, как решение Quassnoi.
SELECT accomodation.ac_id, accomodation.ac_status, accomodation.ac_name, accomodation.ac_status, accomodation.ac_images FROM accomodation, accomodation_category WHERE accomodation.ac_status != 'draft' AND accomodation.ac_category = accomodation_category.acat_id AND accomodation_category.acat_slug != 'vendeglatohely' AND ac_images != 'b:0;' AND rand() <= $size * $factor / [accomodation_table_row_count] LIMIT $size
$size * $factor / [accomodation_table_row_count]
определяет вероятность выбора случайной строки. Rand () сгенерирует случайное число. Строка будет выбрана, если rand () меньше или равна вероятности. Это эффективно выполняет случайный выбор для ограничения размера таблицы. Поскольку существует вероятность того, что он вернет меньше заданного предела, нам нужно увеличить вероятность, чтобы убедиться, что мы выбираем достаточно строк. Следовательно, мы умножаем $ size на $ factor (я обычно устанавливаю $ factor = 2, в большинстве случаев работает). Наконец мы делаемlimit $size
Теперь проблема заключается в том, чтобы разобраться с encodation_table_row_count . Если мы знаем размер таблицы, мы МОЖЕМ жестко закодировать размер таблицы. Это будет работать быстрее всего, но, очевидно, это не идеально. Если вы используете Myisam, подсчет таблиц очень эффективен. Поскольку я использую innodb, я просто делаю простой подсчет + выбор. В вашем случае это будет выглядеть так:
SELECT accomodation.ac_id, accomodation.ac_status, accomodation.ac_name, accomodation.ac_status, accomodation.ac_images FROM accomodation, accomodation_category WHERE accomodation.ac_status != 'draft' AND accomodation.ac_category = accomodation_category.acat_id AND accomodation_category.acat_slug != 'vendeglatohely' AND ac_images != 'b:0;' AND rand() <= $size * $factor / (select (SELECT count(*) FROM `accomodation`) * (SELECT count(*) FROM `accomodation_category`)) LIMIT $size
Сложная часть - вычислить правильную вероятность. Как видите, следующий код фактически вычисляет только приблизительный размер временной таблицы (на самом деле, слишком грубый!):
(select (SELECT count(*) FROM accomodation) * (SELECT count(*) FROM accomodation_category))
Но вы можете уточнить эту логику, чтобы дать более точное приближение к размеру таблицы. Обратите внимание, что лучше выбрать ПЕРЕБРАТЬ, чем выделить строки ниже. то есть, если вероятность слишком низкая, вы рискуете не выбрать достаточно строк.Это решение работает медленнее, чем решение Quassnoi, так как нам нужно пересчитать размер таблицы. Однако я считаю, что это кодирование намного более управляемо. Это компромисс между точностью + производительностью и сложностью кодирования . При этом на больших таблицах это все еще намного быстрее, чем Order by Rand ().
Примечание. Если логика запроса позволяет, выполните случайный выбор как можно раньше перед любыми операциями соединения.
источник
function getRandomRow(){ $id = rand(0,NUM_OF_ROWS_OR_CLOSE_TO_IT); $res = getRowById($id); if(!empty($res)) return $res; return getRandomRow(); } //rowid is a key on table function getRowById($rowid=false){ return db select from table where rowid = $rowid; }
источник