Как оптимизатор исключает ненужные партиции до чтения таблицы?

Как оптимизатор исключает ненужные партиции до чтения таблицы?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Отсечение партиций позволяет оптимизатору определить, какие партиции заведомо не могут содержать подходящие строки, и не включать их в план чтения. Это возможно, когда условие запроса сопоставимо с ограничениями по ключу партиционирования, например содержит подходящий диапазон или равенство.

Если условие скрыто за неподдерживаемой функцией, несовместимым приведением типов или неизвестным на этапе построения плана значением, отсечение может не сработать либо выполняться только во время исполнения. Тогда запрос обращается к большему числу партиций и теряет основное преимущество разбиения.

Исторический контекст

Партиционирование появилось как способ управлять очень большими логическими таблицами, физически разделяя их на меньшие части. Исходная проблема состояла в том, что индекс или последовательное сканирование всей таблицы становились дорогими по объёму чтения, обслуживанию и хранению.

Одного физического разбиения недостаточно: если запрос всё равно читает каждую партицию, выигрыш ограничен. Поэтому оптимизаторы используют знание границ и ограничений партиций, чтобы превратить обращение к большой таблице в чтение только релевантного подмножества.

Постановка проблемы

Предположим, данные разделены по месяцам, а запрос выбирает строки только за один день. При успешном отсечении будут проверены лишь партиции, содержащие этот день. При его отсутствии СУБД может последовательно или индексно просматривать все месячные партиции, хотя большая часть результата заведомо там отсутствует.

Ошибочное ожидание часто возникает из-за самого факта наличия условия по дате. Важно не только наличие фильтра, но и его форма, типы данных, известность значения при построении плана и соответствие выражения границам партиционирования. Лишнее чтение увеличивает I/O, время компиляции и иногда число параллельных задач.

Подробное решение

Для каждой партиции СУБД знает диапазон или множество допустимых значений ключа. Если условие запроса пересекается с таким диапазоном, партиция потенциально релевантна; если пересечение невозможно, она исключается из плана. Например, при разбиении по диапазонам дат фильтр за один месяц обычно оставляет одну или несколько месячных партиций.

Статическое отсечение выполняется при построении плана, когда значение фильтра известно оптимизатору. Динамическое отсечение выполняется во время исполнения, когда значение появляется из параметра, переменной или другой части плана. Конкретная поддержка и момент отсечения зависят от СУБД и типа плана.

Для эффективного отсечения выражение должно быть сопоставимо с ключом партиционирования. Простое сравнение ключа с датой обычно распознаётся лучше, чем вычисление функции над ключом, неявное приведение типов или выражение, результат которого нельзя определить до чтения строк. Даже если оптимизатор не исключает партицию полностью, локальные индексы внутри оставшихся партиций могут дополнительно уменьшить объём чтения.

Партиционирование не заменяет индексы и не гарантирует ускорение любого запроса. Запрос без фильтра по ключу партиционирования может законно читать все партиции, а большое число мелких партиций способно увеличить накладные расходы планирования, открытия объектов и обслуживания. Также нужно учитывать, что глобальные и локальные индексы, статистика и правила отсечения реализуются по-разному в разных СУБД.

Минимальный пример идеи на PostgreSQL:

CREATE TABLE events ( event_id bigint, occurred_at date, payload text ) PARTITION BY RANGE (occurred_at); CREATE TABLE events_2025_01 PARTITION OF events FOR VALUES FROM ('2025-01-01') TO ('2025-02-01'); SELECT event_id FROM events WHERE occurred_at >= DATE '2025-01-10' AND occurred_at < DATE '2025-01-11';

Здесь границы запроса пересекаются только с январской партицией, поэтому оптимизатор может исключить остальные. Проверять результат следует по плану выполнения: в нём должно быть видно обращение не ко всем партициям, а только к подходящим; точное название узла зависит от версии и СУБД.

Ситуация из практики

Таблица событий объёмом в несколько лет была разбита по месяцам. Отчёт за один день неожиданно читал все месяцы, потому что приложение передавало параметр в типе, требующем приведения к типу ключа, а выражение фильтра не распознавалось как ограничение по границам партиций.

Рассматривались три варианта: добавить индексы во все партиции, переписать условие так, чтобы тип параметра совпадал с типом ключа, или отказаться от партиционирования. Индексы могли ускорить чтение внутри каждой партиции, но не устраняли лишнее обращение ко всем месяцам; отказ от разбиения увеличивал бы объём сканирования и усложнял обслуживание.

Выбрали исправление типов и формы предиката, после чего проверили план на нескольких значениях параметра. Это позволило включать в план только нужные партиции, а локальные индексы использовать уже внутри них. Решение было предпочтительным, потому что устраняло первопричину и не увеличивало число индексов.

Что кандидаты часто упускают

  1. Всегда ли фильтр по ключу партиционирования гарантирует отсечение?

Нет. Оптимизатор должен суметь сопоставить выражение фильтра с границами партиций. Функция над ключом, несовместимый тип, сложное логическое условие или неизвестное значение могут помешать статическому отсечению. Поэтому наличие нужного столбца в условии необходимо, но не всегда достаточно.

  1. Чем статическое отсечение отличается от динамического?

При статическом отсечении список релевантных партиций определяется до запуска оператора, обычно по константам запроса. Динамическое отсечение использует значения, которые становятся известны во время выполнения, например параметры или значения из другой части плана. Оно может сохранить эффективность для параметризованных запросов, но его доступность и форма отображения в плане зависят от конкретной СУБД.

  1. Почему после успешного отсечения всё ещё может потребоваться индекс?

Отсечение уменьшает число партиций, но не обязательно уменьшает число строк внутри выбранной партиции. Если одна партиция содержит миллионы строк, для фильтра по другому столбцу всё ещё может быть выгоден локальный индекс или другой способ доступа. Таким образом, партиционирование отвечает преимущественно за исключение крупных диапазонов данных, а индекс — за эффективный поиск внутри оставшегося диапазона.