14.3. Управление планировщиком с явными предложениями JOIN#

14.3. Управление планировщиком с явными предложениями JOIN

14.3. Управление планировщиком с явными предложениями JOIN #

Возможно в некоторой степени контролировать планировщик запросов, используя явный синтаксис JOIN. Чтобы понять, почему это важно, нам сначала нужно немного контекста.

В простом запросе на соединение, например:

SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;

планировщик может соединять заданные таблицы в любом порядке. Например, он может создать план запроса, который соединяет A с B, используя условие WHERE a.id = b.id, а затем соединяет C с этой соединенной таблицей, используя другое условие WHERE. Или он может соединить B с C, а затем соединить A с полученным результатом. Или он может соединить A с C, а затем соединить их с B - но это будет неэффективно, так как полное декартово произведение A и C должно быть сформировано, поскольку в предложении WHERE нет применимого условия для оптимизации соединения. (Все соединения в исполнителе Tantor BEпроисходят между двумя входными таблицами, поэтому необходимо построить результат одним из этих способов). Важно отметить, что эти различные возможности соединения дают семантически эквивалентные результаты, но могут иметь существенно разные затраты на выполнение. Поэтому планировщик будет исследовать все из них, чтобы найти наиболее эффективный план запроса.

Когда запрос включает только две или три таблицы, нет необходимости беспокоиться о множестве порядков соединения. Но количество возможных порядков соединения растет экспоненциально с увеличением числа таблиц. После десяти или около того входных таблиц уже нецелесообразно выполнять исчерпывающий поиск всех возможностей, и даже для шести или семи таблиц планирование может занять неприятно долгое время. Когда входных таблиц слишком много, планировщик Tantor BE переключается с исчерпывающего поиска на генетический вероятностный поиск с ограниченным числом возможностей. (Порог переключения устанавливается параметром времени выполнения geqo_threshold). Генетический поиск занимает меньше времени, но не обязательно находит наилучший возможный план.

Когда запрос включает внешние соединения, планировщик имеет меньше свободы, чем для обычных (внутренних) соединений. Например, рассмотрим:

SELECT * FROM a LEFT JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);

Хотя ограничения этого запроса на первый взгляд похожи на предыдущий пример, семантика отличается, потому что для каждой строки A, которая не имеет соответствующей строки в соединении B и C, должна быть выдана строка. Поэтому планировщик не имеет выбора порядка соединения здесь: он должен объединить B с C, а затем объединить A с этим результатом. Следовательно, этот запрос занимает меньше времени для планирования, чем предыдущий запрос. В других случаях планировщик может определить, что безопасно более одного порядка соединения. Например, учитывая:

SELECT * FROM a LEFT JOIN b ON (a.bid = b.id) LEFT JOIN c ON (a.cid = c.id);

Допустимо сначала соединить A с B или C. В настоящее время только FULL JOIN полностью ограничивает порядок присоединения. Большинство практических случаев, связанных с LEFT JOIN или RIGHT JOIN могут быть в той или иной степени изменены.

Явный синтаксис внутреннего соединения (INNER JOIN, CROSS JOIN или просто JOIN) семантически эквивалентен перечислению входных отношений в FROM, поэтому он не ограничивает порядок соединения.

Несмотря на то, что большинство видов JOIN не полностью ограничивают порядок соединения, возможно указать планировщику запросов Tantor BE рассматривать все предложения JOIN как ограничивающие порядок соединения в любом случае. Например, эти три запроса логически эквивалентны:

SELECT * FROM a, b, c WHERE a.id = b.id AND b.ref = c.id;
SELECT * FROM a CROSS JOIN b CROSS JOIN c WHERE a.id = b.id AND b.ref = c.id;
SELECT * FROM a JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id);

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

Для того чтобы заставить планировщик следовать порядку соединения, заданному явными операторами JOIN, установите параметр времени выполнения join_collapse_limit в значение 1. (Другие возможные значения обсуждаются ниже).

Вам не нужно полностью ограничивать порядок соединения, чтобы сократить время поиска, потому что можно использовать операторы JOIN внутри элементов обычного списка FROM. Например, рассмотрим:

SELECT * FROM a CROSS JOIN b, c, d, e WHERE ...;

С параметром join_collapse_limit = 1 планировщик принуждается объединять таблицы A и B перед их соединением с другими таблицами, но это не ограничивает его выбор в остальном. В этом примере количество возможных порядков соединения уменьшается в 5 раз.

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

Близкая связанная проблема, которая влияет на время планирования, - это схлопывание подзапросов в их родительский запрос. Например, рассмотрим:

SELECT *
FROM x, y,
    (SELECT * FROM a, b, c WHERE something) AS ss
WHERE somethingelse;

Такая ситуация может возникнуть при использовании представления, которое содержит соединение; правило SELECT представления будет вставлено вместо ссылки на представление, что приведет к запросу, похожему на приведенный выше. Обычно планировщик попытается свернуть подзапрос в родительский запрос, получив:

SELECT * FROM x, y, a, b, c WHERE something AND somethingelse;

Это обычно приводит к лучшему плану, чем планирование подзапроса отдельно. (Например, внешние условия WHERE могут быть такими, что соединение X с A сначала устраняет множество строк A, тем самым избегая необходимости формирования полного логического вывода подзапроса). Но в то же время мы увеличили время планирования; здесь у нас есть проблема пятистороннего соединения, заменяющая две отдельные проблемы трехстороннего соединения. Из-за экспоненциального роста числа возможностей это имеет большое значение. Планировщик пытается избежать застревания в огромных проблемах поиска соединения, не сворачивая подзапрос, если более from_collapse_limit элементов FROM приведут к родительскому запросу. Вы можете настроить время планирования в зависимости от качества плана, изменяя это параметр во время выполнения вверх или вниз.

from_collapse_limit и join_collapse_limit названы похожим образом, потому что они делают почти одно и то же: один контролирует когда планировщик будет сглаживать подзапросы, а другой контролирует когда он будет сглаживать явные соединения. Обычно вы бы установили join_collapse_limit равным from_collapse_limit (чтобы явные соединения и подзапросы вели себя похожим образом) или установили бы join_collapse_limit в 1 (если нужно контролировать порядок соединения с явными соединениями). Но вы можете установить их по-разному, если вы пытаетесь настроить компромисс между временем планирования и временем выполнения.