Join создал дублирующие строки, и кто-то добавил SELECT DISTINCT — почему это дорого?
Этот отчёт соединил orders с таблицей order_items «один-ко-многим», что продублировало каждую строку заказа, поэтому кто-то добавил DISTINCT, чтобы спрятать дубли.
Ограничение: отчёту нужна одна строка на заказ — строки позиций не агрегируются и не выбираются.
SELECT DISTINCT o.order_id, o.user_id, o.status, o.created_at
FROM orders o
JOIN order_items i ON i.order_id = o.order_id;
Объясните, почему DISTINCT здесь дорог, и почините корень проблемы.
DISTINCT сортирует или хэширует каждый столбец разросшегося результата, убирая дубли — дорого на широких fan-out-строках, и лишь маскирует ошибку. Дубли идут от join «один-ко-многим»; чините причину через WHERE EXISTS (...) (semi-join) или сверните до одной строки на ключ.
- ✗Думать, что DISTINCT — дешёвый потоковый флаг, а не сортировка или хэш
- ✗Считать, что LEFT JOIN даст одну строку на заказ вопреки fan-out
- ✗Затыкать дубли через DISTINCT вместо устранения fan-out от join
- →Как EXPLAIN покажет DISTINCT как узел Sort или HashAggregate?
- →Когда EXISTS быстрее пред-агрегации дочерней таблицы?
DISTINCT заставляет отсортировать или захэшировать все четыре выбранных столбца по всему разросшемуся результату, чтобы убрать дубли — на широких строках это дорогой шаг сортировки/хэша, и он лишь прячет настоящую причину. Дубли создаёт join «один-ко-многим»: каждая позиция order_items порождает копию строки заказа.
Раз таблица позиций нужна только чтобы проверить, что у заказа есть хотя бы одна позиция, замените join на semi-join:
SELECT o.order_id, o.user_id, o.status, o.created_at
FROM orders o
WHERE EXISTS (
SELECT 1 FROM order_items i WHERE i.order_id = o.order_id
);
EXISTS не размножает строки, поэтому DISTINCT не нужен, а планировщик останавливается на первом совпадении. Если же понадобятся агрегаты по позициям, сверните order_items подзапросом с GROUP BY order_id и присоединяйте уже одну строку на заказ.