Если нужно вытащить случайные строки из таблицы, многие пишут так, например в PostgreSQL:
SELECT *
FROM products
ORDER BY RANDOM()
LIMIT 10;
На первый взгляд всё отлично: перемешали строки и взяли первые 10. Но под капотом всё не так красиво. Обычно база читает много строк, а часто вообще всю таблицу, затем вычисляет случайное значение для каждой строки, сортирует результат и только после этого применяет
LIMIT.То есть даже если нужна всего одна строка:
SELECT *
FROM products
ORDER BY RANDOM()
LIMIT 1;
на большой таблице это всё равно может оказаться тяжёлым запросом. Если строк миллионы, начинаются проблемы с CPU, памятью, сортировками и latency. Индексы здесь обычно не помогают.
Один из более дешёвых вариантов — случайный выбор через id:
SELECT *
FROM products
WHERE id >= 1 + FLOOR(RANDOM() * (SELECT MAX(id) FROM products))::int
ORDER BY id
LIMIT 1;
Этот вариант уже может использовать индекс по id. Но есть нюанс: если после удалений в id много дырок, распределение будет неидеальным.
Например:
id
1
2
3
10000
10001
В таком случае чаще будет выпадать первая существующая строка после большого разрыва — здесь это 10000. То есть выборка получается смещённой.
Ещё один вариант — случайный
OFFSET:SELECT *
FROM products
OFFSET FLOOR(RANDOM() * (SELECT COUNT(*) FROM products))::int
LIMIT 1;
Звучит неплохо. Но у
OFFSET тоже есть проблема: чем больше offset, тем больше строк базе придётся пропустить. Плюс в PostgreSQL COUNT(*) на большой таблице тоже может быть дорогой операцией.В PostgreSQL есть ещё
TABLESAMPLE:SELECT *
FROM products TABLESAMPLE SYSTEM (1);
Или:
SELECT *
FROM products TABLESAMPLE BERNOULLI (1);
На больших таблицах это часто работает заметно быстрее. Но важно понимать:
TABLESAMPLE выбирает примерный процент таблицы, а не конкретное количество строк.Если нужно, например, 10 строк, обычно добавляют
LIMIT:SELECT *
FROM products TABLESAMPLE SYSTEM (1)
LIMIT 10;
Но и тут есть нюанс: если sample слишком маленький, запрос может вернуть меньше 10 строк.
И здесь тоже есть компромисс между скоростью и качеством случайной выборки.
SYSTEM работает быстрее, но выбирает данные блоками, поэтому распределение менее равномерное. BERNOULLI ближе к построчной случайной выборке, но обычно дороже. В общем, мысль простая:ORDER BY RANDOM()
выглядит красиво и удобно. Но на больших таблицах это один из тех запросов, которые начинают стоить очень дорого.
🔥 Именно такие простые запросы часто становятся причиной деградации производительности в продакшн.
➡️ SQL Ready | #практика