Использование rand() не вычисляет все, прежде чем дать результат
Хорошо, у меня есть следующий запрос:
SELECT f1.id id1,f1.filename filename1,f2.id id2, f2.filename filename2
FROM files f1
JOIN files f2 on f1.id < f2.id
WHERE (f1.id,f2.id) NOT IN(
SELECT id1,id2 FROM alreadyCalculatedCombinations
)
ORDER BY RAND()
LIMIT 1;
Весь запрос занимает около 4,5 секунд. select id1,id2 from alreadyCalculatedCombinations
занимает 0,1 секунды. the query without the WHERE and ORDER BY RAND() and LIMIT 1
4,5 секунды the query without the ORDER BY RAND()
занимает 0,1 секунды.
Вывод: mysql рассчитывает каждую комбинацию из f1
а также f2
затем рандомизировать его, а затем выбирает один ряд.
Очевидно, что интернет (см. http://jan.kneschke.de/projects/mysql/order-by-rand/) был довольно полезен, за исключением одной вещи: WHERE (f1.id,f2.id) NOT IN
является условием, и если в созданном случайном идентификаторе нет строк, я облажался.
Решение (почти идеальное):
Вместо того, чтобы вычислять каждую строку, мы вычислим 2 строки (в идеале должна быть одна, но эй...) (мой запрос сам по себе использует 4, но я рассмотрю более простой случай для вас, ребята)
Шаг 1 Создайте несколько случайных идентификаторов, а затем попытайтесь вернуть эту строку (я заметил, что запрос с вложенными представлениями или просто множеством представлений выполняется быстрее, чем один запрос типа humungus, также облегчает выполнение)
CREATE OR REPLACE VIEW randomId AS
SELECT
RAND()*(SELECT MAX(id)-MIN(id) FROM files)+(SELECT MIN(id) FROM files) AS id1 ;
#the above link uses only max id, but my table has ids from 1200 to 2500, read bellow why, and half of the times my query gave me the same row and that's why I use min too
Шаг 2: поместите большой запрос в одно представление
CREATE OR REPLACE VIEW bigQuery AS
SELECT f1.id id1,f1.filename filename1,f2.id id2, f2.filename filename2
#bla bla, boring query
Шаг 3: найдите 1 строку и никогда не возвращайте пустой набор
SELECT id1,filename1,id2 ,filename2
FROM (
SELECT id1,filename1,id2 ,filename2 #container query
FROM (
SELECT b.id1,b.filename1,b.id2,b.filename2
FROM bigQuery AS b
JOIN randomId as r
WHERE base.id1 >= r.id1 #as you can see we pick a random id, and >= just in case that id does not meet the "WHERE NOT IN" condition
LIMIT 1
) idealRandom #we need this container query because we are not allowed to have a LIMIT before UNION
UNION
SELECT id1,filename1,id2 ,filename2 #conainter query again
FROM (
SELECT b.id1,b.filename1,b.id2,b.filename2
FROM bigQuery AS b
JOIN randomId as r
WHERE b.id1 < randomIds.id1
LIMIT 1
) startAgainFromTheBeggining #we need this container query too, beucase if the limit is applied to a union. mysql wants to join the union for no logical reason I can think, then do a full scan on the whole union and after that limiting, instead of just stoping the full scan after 1 row
) twoRows #now we should have 2 rows or one
ORDER BY id1 desc #if no row with id>=random passed then use the one <random. Keep in mind that startAgainFromTheBeggining will always give the same result (unless you dont use primary keeys or you delete rows) because while idealRow will start querying from let's say id>1550, startAgain will look for id<1550, but will start from id=1 always.
PS: я пытался сделать select idealRandom union select startAgain limit 1
, но это заняло огромное количество времени. Mysql попытался выполнить полное сканирование объединения (после полного вычисления строк), а затем отбросил строки ALL-1. Обычно я думаю, что объединение должно было быть остановлено после того, как найдена 1 строка, то есть полное сканирование, которое останавливается после того, как что-то находит. Но, конечно, в идеале mysql заметил бы, что первая строка (учитывая, что чтение строк начинается с id1) соответствовала бы id