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