1.1Kпросмотров
83.4%от подписчиков
18 октября 2025 г.
📷 ФотоScore: 1.2K
Порядок выполнения SQL‑операторов Запрос пишем слева направо, но в каком порядке его выполняет СУБД?
Мы пойдём верхнеуровнево, то есть без деталей конкретных алгоритмов планировщика и executor-а. Цель – сложить в голове правильную модель, на которой вы сможете построить больше уровней абстракций и разобраться с деталями реализации. Это логический порядок обработки данных в SQL, а не физический порядок исполнения плана. Реальный порядок может отличаться! Пример запроса
SELECT DISTINCT u.id, u.name, AVG(o.total) AS avg_order
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.active = true AND o.created_at >= date_trunc('month', now())
GROUP BY u.id, u.name
HAVING AVG(o.total) > 100
ORDER BY avg_order DESC
LIMIT 10 OFFSET 5;
Мы хотим: 10 активных пользователей за текущий месяц со средним чеком заказа > 100, отсортированных по убыванию их среднего чека. 1. FROM
База начинает с того, что планировщик выбирает оптимальный способ доступа к таблицами и оценивает, сколько строк в каждой, какие есть индексы, какие столбцы понадобятся.
FROM users u 2. JOIN
Теперь таблицы соединяются.
JOIN orders o ON o.user_id = u.id
После JOIN PostgreSQL создаёт поток соединённых строк. Это не новая таблица, а временная структура в памяти, с которой он работает дальше. Тип соединения выбирается на основе плана: Nested Loop, Hash Join, Merge Join. 3. WHERE
Фильтрация лишних строк – всё, что не пройдёт условие, выкидывается.
WHERE u.active = true AND o.created_at >= date_trunc('month', now())
Чем раньше база отсеет ненужное, тем меньше ей потом группировать, сортировать и агрегировать. На практике данный этап может быть объединен с JOIN. 4. GROUP BY
Группировка оставшихся строк.
GROUP BY u.id, u.name
Теперь база собирает строки по ключам, например, все заказы одного пользователя, и готовится считать агрегаты (AVG, SUM, COUNT и др.). 5. HAVING
Второй фильтр, только теперь по сгруппированным данным.
HAVING AVG(o.total) > 100
WHERE работает с отдельными строками, а HAVING – с группами.
Если средний чек < 100, то пользователь исключается из результата. 6. SELECT
Теперь выбираются нужные колонки и вычисляются выражения. SELECT u.id, u.name, AVG(o.total) AS avg_order
Это момент, когда результат начинает “принимать форму” будущей таблицы. 7. DISTINCT
Удаляются дубликаты:
SELECT DISTINCT ...
Чтобы выполнить DISTINCT, PostgreSQL либо сортирует строки, чтобы сравнить подряд, либо строит хеш-таблицу, но в обоих случаях это требует времени и памяти. 8. ORDER BY
Сортировка результата.
ORDER BY avg_order DESC
Если есть подходящий индекс, сортировка может быть почти бесплатной. Если нет, PostgreSQL сортирует набор строк, полученных после всех предыдущих стадий. При большом объёме данных может потребоваться временный файл (tmp) на диске, что называется внешней сортировкой. 9. LIMIT / OFFSET
Отбираются нужные строки:
LIMIT 10 OFFSET 5
Если порядок уже известен (ORDER BY), PostgreSQL может остановиться после получения нужного количества строк. Это экономит ресурсы. SQL-запросы выполняются не в том порядке, как мы их пишем. Это важно, потому что структура запроса напрямую влияет на то, сколько данных придётся читать, фильтровать, сортировать. Если представить, что у нас обычный жёсткий диск(hdd), что достаточно архаично с дешевыми SSD, то диски любят последовательное чтение. Вращающаяся пластина и механическая головка: чтобы считать данные, ей нужно доехать до нужной дорожки (seek-time) и дождаться сектора. Случайные чтения по рандомным местам здесь дорогие. Чем раньше ты отфильтруешь ненужное (WHERE), тем меньше случайных обращений к данным и тем быстрее всё остальное. Планировщик старается минимизировать объём данных, проходящих через стадии: чем раньше фильтрация, тем меньше нужно сортировать и группировать.
Фильтрация раньше — меньше строк → меньше сортировок → меньше временных файлов. Индексы + порядок → можно просто прочитать данные по прямой, без прыжков по диску.