
Вот структурированная выжимка в формальном академическом стиле, оптимизированная для Obsidian.
Внутреннее устройство и выполнение SQL-запросов (Query Planner)
Теги: sql databases interview queryplanner performance
SQL является декларативным языком программирования: разработчик описывает требуемый результат, а СУБД (через компонент Query Planner/Optimizer) самостоятельно определяет наиболее эффективный алгоритм извлечения данных.
1. Порядок написания vs Порядок выполнения
Фундаментальное правило для понимания SQL — различие между синтаксической структурой запроса и логической последовательностью его обработки ядром СУБД.
✍️ Порядок написания (Синтаксический)
SELECT(выборка столбцов)FROM(основная таблица)JOIN(присоединяемые таблицы)WHERE(фильтрация строк)GROUP BY(группировка)HAVING(фильтрация групп)ORDER BY(сортировка)LIMIT / OFFSET(ограничение выборки)
⚙️ Порядок выполнения (Логический)
FROMиJOIN— определение источников данных и формирование декартова произведения/соединения.WHERE— первичная фильтрация строк (до агрегации). Именно поэтому здесь недоступны алиасы из SELECT.GROUP BY— формирование групп на основе заданных ключей.HAVING— вторичная фильтрация (применяется к уже сформированным группам).SELECT— проекция данных: вычисление выражений, функций, применение алиасов.ORDER BY— сортировка результирующего набора.LIMIT / OFFSET— финальное усечение количества возвращаемых строк.
2. Жизненный цикл SQL-запроса
При поступлении запроса в СУБД он проходит 4 основные стадии:
- Парсер (Parser): Лексический и синтаксический анализ. Проверка на соответствие грамматике SQL.
- Анализатор (Analyzer / Binder): Семантический анализ. Проверка существования таблиц/столбцов, валидация типов данных и проверка прав доступа (ACL).
- Оптимизатор (Query Optimizer / Planner): Генерация возможных планов выполнения и выбор оптимального (с наименьшей стоимостью).
- Исполнитель (Executor): Физическое выполнение выбранного плана (обращение к памяти и дисковой подсистеме).
3. Механизмы работы Оптимизатора (Cost-Based Optimizer)
Современные СУБД используют CBO (Cost-Based Optimizer) — оптимизатор на основе стоимости. Для оценки стоимости (Cost) различных планов выполнения СУБД использует статистику, которая включает:
- Общее количество строк в таблицах (Tuples).
- Кардинальность (количество уникальных значений в столбцах).
- Наличие и тип индексов.
- Гистограммы распределения данных (помогают оценить селективность условия
WHERE).
4. Физические операторы (Как СУБД обрабатывает данные)
Оптимизатор транслирует логические операции в физические алгоритмы.
Методы доступа к данным (Access Methods)
- Sequential Scan (Full Table Scan): Последовательное чтение всей таблицы с диска. Применяется при отсутствии индексов или если запрос требует чтения большей части таблицы (низкая селективность).
- Index Scan: Поиск идентификаторов строк (RowID/CTID) в B-Tree индексе с последующим обращением к таблице за самими данными.
- Index Only Scan: Извлечение данных исключительно из индекса (без обращения к таблице), если индекс содержит все запрашиваемые столбцы (Covering Index).
Алгоритмы соединения таблиц (Join Algorithms)
- Nested Loop Join (Вложенные циклы): Итеративный обход: для каждой строки из внешней таблицы ищется совпадение во внутренней. Эффективен при малых объемах данных и наличии индексов на ключах соединения.
- Hash Join (Хэш-соединение): Построение хэш-таблицы в оперативной памяти из меньшей таблицы, затем сканирование большей таблицы с поиском по хэшу. Оптимален для больших объемов неиндексированных данных.
- Merge Join (Соединение слиянием): Параллельный проход по двум предварительно отсортированным наборам данных. Наиболее эффективен, если данные уже отсортированы (например, извлечены через Index Scan).
Алгоритмы агрегации (GROUP BY)
- HashAggregate: Построение хэш-таблицы в памяти для подсчета агрегатов. Быстро, но требовательно к RAM.
- GroupAggregate: Требует предварительной сортировки данных по ключу группировки. Используется при нехватке памяти или если данные уже отсортированы.
Сортировка (ORDER BY)
- In-memory Sort (QuickSort): Выполняется в оперативной памяти (в PostgreSQL ограничено параметром
work_mem). - External Merge Sort: Выполняется с использованием временных файлов на диске. Крайне медленная операция, которой следует избегать путем создания индексов.
5. Ключевые концепции для собеседований
- Predicate Pushdown (Проталкивание предикатов): Оптимизационная техника, при которой условия из блока
WHEREприменяются на самом раннем этапе (до выполненияJOIN), чтобы минимизировать объем данных, передаваемых на следующие узлы обработки. - Влияние LIMIT на план запроса: Наличие
LIMITможет радикально изменить план выполнения. Оптимизатор может отказаться отHash Join(требующего полного сканирования) в пользуNested Loop, чтобы прервать выполнение сразу после нахождения нужного количества строк (Short-circuiting). - Анализ производительности: Для диагностики неоптимальных запросов используется команда
EXPLAIN(показывает предполагаемый план) иEXPLAIN ANALYZE(выполняет запрос и показывает реальное время и количество строк на каждом узле плана).
В SQL работа с датами немного отличается в зависимости от СУБД (PostgreSQL, MySQL, Oracle, MS SQL), но основные сценарии у всех одинаковые.
Ниже приведен список самых важных функций, разбитых по задачам. Основной синтаксис дан по стандарту ANSI SQL / PostgreSQL (с пометками для MySQL).
Работа с датой и временем
Типы данных
| Тип | Что хранит | Пример | Функции, возвращающие этот тип | Полезные функции и операции для работы с типом |
|---|---|---|---|---|
| DATE | Год-Месяц-День | 2023-12-31 | • CURRENT_DATE • CAST(NOW() AS DATE) | • Сложение/вычитание дней: CURRENT_DATE + 5 • Разница в днях: date_1 - date_2 (вернет число INT) |
| TIME | Час:Мин:Сек | 18:00:00 | • CURRENT_TIME • LOCALTIME | • Сложение с интервалом: TIME ‘12:00’ + INTERVAL ‘30 minutes’ |
| TIMESTAMP | Дата + Время (без таймзоны) | 2023-12-31 18:00:00 | • LOCALTIMESTAMP • NOW()::TIMESTAMP | • Приведение к началу месяца/года: DATE_TRUNC(‘month’, ts) • Вычитание двух таймстампов: ts1 - ts2 (вернет INTERVAL) |
| TIMESTAMPTZ | Дата + Время (с таймзоной) | 2023-12-31 18:00:00+03 | • NOW() • CURRENT_TIMESTAMP • CLOCK_TIMESTAMP() (текущее время в процессе выполнения) | • Смена таймзоны на лету: NOW() AT TIME ZONE ‘UTC’ • Срез точности: DATE_TRUNC(‘day’, NOW()) |
| INTERVAL | Промежуток (длительность) | 1 day 04:00:00 | • AGE(timestamp1, timestamp2) (разница между датами) • MAKE_INTERVAL(days => 5) | • Добавление к датам: NOW() - INTERVAL ‘3 hours’ • Умножение/деление: INTERVAL ‘1 hour’ * 2 |
1. Текущая дата и время
CURRENT_DATE -- 2024-05-15 (тип DATE)
CURRENT_TIME -- 14:30:00 (тип TIME)
CURRENT_TIMESTAMP, NOW() -- 2024-05-15 14:30:00.123+03 (тип TIMESTAMPTZ)2. Извлечение частей даты (EXTRACT)
EXTRACT(YEAR FROM col) -- Год (2024)
EXTRACT(MONTH FROM col) -- Месяц (1..12)
EXTRACT(DAY FROM col) -- День месяца (1..31)
EXTRACT(DOW FROM col) -- День недели (0 - Вс, 1 - Пн ... 6 - Сб)
EXTRACT(EPOCH FROM col) -- Unix timestamp в секундах3. Округление даты (DATE_TRUNC) — для GROUP BY
Обрезает дату до начала указанного интервала (возвращает TIMESTAMP).
DATE_TRUNC('day', col) -- 2024-05-15 00:00:00
DATE_TRUNC('week', col) -- Начало недели (Пн, 00:00:00)
DATE_TRUNC('month', col) -- 2024-05-01 00:00:00
DATE_TRUNC('year', col) -- 2024-01-01 00:00:004. Арифметика с интервалами (INTERVAL)
col + INTERVAL '3 days' -- Прибавить 3 дня
col - INTERVAL '1 month' -- Вычесть 1 месяц
col + INTERVAL '2 hours 30 mins' -- Прибавить 2.5 часа
CURRENT_DATE - INTERVAL '7 days' -- Ровно неделю назад от текущего дня5. Разница между датами и Возраст
date1 - date2 -- Разница в днях (целое число: 15)
AGE(date1, date2) -- Интервал: '1 year 2 mons 3 days'
AGE(birth_date) -- Возраст относительно CURRENT_DATE
EXTRACT(YEAR FROM AGE(birthday)) -- Полных лет (число)6. Преобразование и Форматирование
| Что нужно сделать | Функция / Синтаксис | Пример | Результат |
|---|---|---|---|
| Из даты в строку (свои форматы) | TO_CHAR(дата, 'формат') | TO_CHAR(NOW(), 'DD.MM.YYYY HH24:MI') | '29.08.2026 16:40' (тип text) |
| Из строки в Дату | TO_DATE('строка', 'формат') | TO_DATE('25-10-2023', 'DD-MM-YYYY') | 2023-10-25 (тип date) |
| Из строки в Timestamp | TO_TIMESTAMP('стр', 'форм') | TO_TIMESTAMP('2023-10-25 14:30', 'YYYY-MM-DD HH24:MI') | 2023-10-25 14:30:00+00 |
| Обрезать время (Timestamp → Date) | значение::date или CAST | NOW()::date | 2026-08-29 (тип date) |
| Стандартная строка ISO в TimestampTZ | строка::timestamptz | '2018-10-09T00:00:00Z'::timestamptz | 2018-10-09 00:00:00+00 |
| Timestamp в Unix-время (секунды) | EXTRACT(EPOCH FROM дата) | EXTRACT(EPOCH FROM NOW()) | 1787928012.123 (число) |
| Из Unix-времени в Timestamp | TO_TIMESTAMP(число) | TO_TIMESTAMP(1698244200) | 2023-10-25 14:30:00+00 |
📌 Констрейнты (Ограничения целостности) в SQL
Констрейнты задают правила валидации данных на уровне движка СУБД. При нарушении правила транзакция откатывается с ошибкой.
1. Именование констрейнтов (Конвенция)
| Префикс | Тип ограничения | Пример имени |
|---|---|---|
pk_ | Primary Key | pk_users |
fk_ | Foreign Key | fk_orders_user_id |
uq_ | Unique Constraint | uq_users_email |
chk_ | Check Constraint | chk_products_price_positive |
excl_ | Exclusion Constraint (PG) | excl_bookings_no_overlap |
2. Синтаксис создания (ALTER TABLE)
-- 1. PRIMARY KEY
ALTER TABLE users
ADD CONSTRAINT pk_users PRIMARY KEY (id);
-- 2. FOREIGN KEY (с каскадными действиями)
-- Варианты ON DELETE / ON UPDATE: RESTRICT, CASCADE, SET NULL, NO ACTION
ALTER TABLE orders
ADD CONSTRAINT fk_orders_user_id
FOREIGN KEY (user_id) REFERENCES users(id)
ON DELETE RESTRICT
ON UPDATE CASCADE;
-- 3. UNIQUE (одиночный и составной)
ALTER TABLE users
ADD CONSTRAINT uq_users_email UNIQUE (email);
ALTER TABLE user_roles
ADD CONSTRAINT uq_user_roles_user_role UNIQUE (user_id, role);
-- 4. CHECK (логические выражения над колонками)
ALTER TABLE products
ADD CONSTRAINT chk_products_price CHECK (price > 0);
ALTER TABLE bookings
ADD CONSTRAINT chk_bookings_dates CHECK (end_date > start_date);
-- 5. NOT NULL (синтаксис отличается от остальных)
ALTER TABLE users
ALTER COLUMN email SET NOT NULL;
-- 6. EXCLUDE (PostgreSQL: исключение пересечений)
CREATE EXTENSION IF NOT EXISTS btree_gist;
ALTER TABLE bookings
ADD CONSTRAINT excl_bookings_no_overlap
EXCLUDE USING gist (room_id WITH =, daterange(start_date, end_date) WITH &&);