Как механизм адаптивного соединения выбирает между nested loops и hash join во время выполнения запроса?
Адаптивное соединение откладывает окончательный выбор алгоритма до выполнения запроса. Оно подсчитывает фактическое число строк на входе: при объёме ниже заданного порога использует nested loops, а при превышении порога переключается на hash join. Это снижает риск неудачного выбора, когда оценка кардинальности при построении плана была неточной.
Классический оптимизатор выбирает алгоритм соединения до запуска запроса, опираясь на статистику и оценочные количества строк. Если оценка ошибочна, оптимизатор может выбрать nested loops для большого набора данных или hash join для маленького.
Адаптивные соединения появились как способ отложить чувствительную к объёму данных часть решения до момента, когда фактический объём уже частично известен. Подход особенно полезен при нестабильном распределении данных и неточных оценках кардинальности.
Nested loops обычно выгоден, когда внешний вход мал, а внутренний источник эффективно ищется по индексу. При большом числе строк он может многократно выполнять поиск и стать очень дорогим.
Hash join обычно лучше подходит для крупных входов, но требует построения хеш-структуры, памяти и чтения значительного объёма данных. Для нескольких строк эта подготовка может оказаться дороже самих точечных поисков.
Если план заранее зафиксирован на одном алгоритме, один и тот же запрос может хорошо работать для одних параметров и плохо — для других. Адаптивный оператор уменьшает этот риск, но не устраняет ошибки статистики и накладные расходы полностью.
Адаптивный оператор получает оценочный порог, рассчитанный оптимизатором. Во время выполнения он читает строки с одного из входов и отслеживает их фактическое количество.
Если число строк остаётся ниже порога, оператор выбирает nested loops. В этом случае небольшой внешний набор используется для поиска соответствующих строк во втором входе, обычно через индекс.
Если число строк превышает порог, оператор выбирает hash join. Строки используются для построения хеш-структуры, после чего второй вход проверяется по хеш-ключам. Такой вариант избегает большого количества повторных поисков.
Порог не является универсальным свойством алгоритма: он зависит от оценок стоимости, стоимости доступа к данным, доступной памяти и характеристик конкретного плана. Если вход пересекает порог поздно, часть работы по чтению и подготовке уже выполнена, поэтому адаптивность сама по себе не делает любой план оптимальным.
Механизм не заменяет актуальную статистику. При систематически неверных оценках оптимизатор может неправильно рассчитать порог, выбрать неподходящие индексы или вообще не построить адаптивный план. Кроме того, адаптивные операторы поддерживаются не всеми СУБД и не для всех типов соединений и режимов выполнения.
Запрос соединяет таблицу клиентов с заказами. Для одного запуска фильтр выбирает несколько клиентов, а для другого — значительную долю таблицы. Для малого результата nested loops с индексом по идентификатору клиента обычно экономит чтения, но при большом результате вызывает множество обращений к таблице заказов.
Рассматривались два фиксированных варианта. Принудительный nested loops был бы эффективен для малых выборок, но мог резко замедлиться на крупных; принудительный hash join лучше переносил большие объёмы, но создавал лишнюю работу для малых. Адаптивное соединение выбрали как компромисс: план получил возможность переключаться по фактическому размеру входа.
При внедрении проверили фактические планы, потребление памяти и случаи переключения. Если распределение данных изменится так, что порог станет неудачным, потребуется повторно проверить статистику и стоимость операторов, а не полагаться на сам факт наличия адаптивного соединения.
Вопрос: заменяет ли адаптивное соединение обновление статистики?
Нет. Оно реагирует на фактическое число строк во время выполнения, но не исправляет статистику, на которой построены сам план, порог и остальные решения оптимизатора. Устаревшая статистика может привести к плохому выбору индексов, порядка соединений или недостаточному выделению памяти даже при наличии адаптивного оператора.
Вопрос: почему адаптивное соединение не гарантирует минимальное время выполнения?
Для выбора алгоритма нужно выполнить часть работы и определить фактический объём входа. Кроме того, адаптивный план может иметь дополнительные требования к памяти и поддерживается не во всех сценариях. Если фактическое распределение данных отличается от ожидаемого или порог рассчитан неточно, выбранный вариант всё равно может быть не лучшим.
Вопрос: чем адаптивное соединение отличается от параметрического выбора плана?
При параметрическом выборе плана обычно выбирается и кэшируется один вариант, основанный на конкретном первом выполнении или на текущих оценках. Адаптивное соединение сохраняет один план, но оставляет выбор между двумя алгоритмами до выполнения и принимает его по фактическому объёму строк. Это разные механизмы: адаптивность уменьшает чувствительность к кардинальности, но не решает все проблемы кэширования планов.