1. Принципы работы оптимизатора и анализ планов выполнения (EXPLAIN)
Принципы работы оптимизатора и анализ планов выполнения (EXPLAIN)
SQL является декларативным языком. Это означает, что вы описываете системе, какие данные вы хотите получить, но не указываете, как именно их нужно извлечь. Определение алгоритма поиска, соединения таблиц и фильтрации — это задача РСУБД, а конкретнее — её компонента, называемого оптимизатором запросов.
Понимание логики работы оптимизатора — это фундамент для написания производительного кода. Без этого знания любые попытки ускорить базу данных будут напоминать гадание.
!Этапы жизни SQL-запроса от отправки до получения результата
Архитектура обработки запроса
Прежде чем запрос начнет выполняться, он проходит несколько стадий:
WHERE id = 1 + 1 превращается в WHERE id = 2).Cost-Based Optimization (CBO)
Большинство современных реляционных баз данных (PostgreSQL, Oracle, MS SQL Server) используют стоимостную оптимизацию (Cost-Based Optimization). Главная цель CBO — найти план выполнения с наименьшей ожидаемой стоимостью (Cost).
Стоимость (Cost) — это абстрактная величина, которая не измеряется в секундах или миллисекундах. Это условная единица, отражающая затраты ресурсов (дисковые операции ввода-вывода, процессорное время, использование памяти) на выполнение операции.
Обычно за базовую единицу (1.0) принимается стоимость последовательного чтения одной страницы данных с диска. Все остальные операции оцениваются относительно этой базы.
Формула расчета стоимости
Рассмотрим упрощенную модель расчета стоимости для последовательного сканирования таблицы (Sequential Scan). Оптимизатор использует формулу:
Где: * — итоговая стоимость операции. * — количество страниц данных, занимаемых таблицей на диске. * — стоимость чтения одной страницы (обычно 1.0). * — количество строк (кортежей) в таблице. * — стоимость обработки одной строки процессором (обычно очень малое число, например, 0.01).
Если таблица занимает 100 страниц и содержит 10 000 строк, а коэффициенты стандартные (, ), то расчет будет следующим:
Где: * — итоговая стоимость, равная 200 условным единицам. * — затраты на чтение диска. * — затраты процессора на обработку строк.
Оптимизатор сравнивает этот результат со стоимостью использования индекса. Если чтение через индекс будет стоить 150, система выберет индекс. Если 250 — выберет полное сканирование.
Роль статистики
Оптимизатор не заглядывает в реальные файлы данных при построении плана — это было бы слишком долго. Вместо этого он использует статистику — метаданные о распределении данных в таблицах.
Ключевые метрики статистики: * Приблизительное количество строк в таблице. * Количество уникальных значений в колонке (cardinality). * Гистограммы распределения значений (помогают понять, как часто встречается конкретное значение). * Количество NULL-значений.
Если статистика устарела (например, вы добавили миллион строк, но не обновили статистику), оптимизатор будет принимать решения на основе ложных данных. Это частая причина падения производительности.
Анализ планов выполнения (EXPLAIN)
Для взаимодействия с оптимизатором используется команда EXPLAIN. Она показывает дерево операций, которое СУБД планирует выполнить.
В PostgreSQL и многих других системах существует два режима:
EXPLAIN — показывает предполагаемый план и стоимость без выполнения запроса.EXPLAIN ANALYZE — выполняет запрос реально, показывает плановую стоимость И реальное время выполнения.Пример вывода EXPLAIN (упрощенный):
Планы читаются снизу вверх и изнутри наружу. В примере выше:
users.orders.orders сопоставляется с хеш-таблицей пользователей.!Иерархия операций в плане выполнения: данные движутся снизу вверх
Основные методы доступа к данным
При анализе плана вы чаще всего столкнетесь со следующими операциями:
1. Сканирование таблиц (Scan)
* Seq Scan (Full Table Scan): Последовательное чтение всей таблицы. Эффективно для маленьких таблиц или когда нужно прочитать большую часть данных (более 20-30%). * Index Scan: Поиск по B-Tree индексу. Эффективно для выборки малого количества строк (селективные запросы). * Index Only Scan: Данные берутся прямо из индекса, обращение к основной таблице (куче) не требуется. Самый быстрый метод.
2. Соединение таблиц (Join)
Оптимизатор выбирает алгоритм соединения в зависимости от объема данных и наличия памяти:
* Nested Loop Join (Вложенные циклы): Берется строка из одной таблицы и ищется соответствие в другой. Идеально, когда одна из таблиц очень маленькая, а во второй есть индекс по ключу соединения. Аналогия:* У вас есть список из 5 друзей, и вы ищете их номера в телефонной книге. * Hash Join: Строится хеш-таблица из меньшей таблицы, затем вторая таблица сканируется и проверяется по хешу. Отлично подходит для больших несортированных наборов данных. * Merge Join: Требует, чтобы оба набора данных были отсортированы по ключу соединения. Эффективно для очень больших выборок, где Hash Join не помещается в память.
Итоги
* Оптимизатор — это мозг СУБД, который преобразует декларативный SQL в процедурный план выполнения, используя стоимостную модель (CBO).
* Стоимость (Cost) — это не время, а условная единица ресурсов ввода-вывода и процессора. Оптимизатор выбирает план с наименьшей суммарной стоимостью.
* Качество плана напрямую зависит от актуальности статистики. Устаревшая статистика ведет к неоптимальным решениям.
* Команда EXPLAIN позволяет увидеть стратегию выполнения запроса. Чтение плана происходит снизу вверх.
* Основные операции в плане — это методы доступа (Seq Scan, Index Scan) и методы соединения (Nested Loop, Hash Join, Merge Join). Выбор метода зависит от объема данных и селективности условия.