18.2.25

Подразумеваемые (Implied) предикаты

Автор: Craig Freedman Implied Predicates and Query Hints

В этой статье будут рассмотрены некоторые странности с предикатами в планах запросов. Рассмотрим следующую тривиальную схему и запрос:

CREATE TABLE T1 (A INT, B INT)

CREATE TABLE T2 (A INT, B INT)

 

SELECT *

FROM T1 INNER JOIN T2 ON T1.A = T2.A

WHERE T1.B = 0

OPTION (HASH JOIN)

Как и задумывалось, этот запрос будет выполняться со следующим планом:

  |--Hash Match(Inner Join, HASH:([T1].[A])=([T2].[A]), RESIDUAL:([T2].[A]=[T1].[A]))

       |--Table Scan(OBJECT:([T1]), WHERE:([T1].[B]=(0)))

       |--Table Scan(OBJECT:([T2]))

На самом деле, этот запрос получит такой план с подсказкой или без нее. Теперь давайте внесем небольшое изменение в предложение WHERE и посмотрим, что произойдет:

SELECT *

FROM T1 INNER JOIN T2 ON T1.A = T2.A

WHERE T1.A = 0

OPTION (HASH JOIN)

Теперь этот запрос выдает сообщение об ошибке:

Msg 8622, Level 16, State 1, Line 1

Query processor could not produce a query plan because of the hints defined in this query. Resubmit the query without specifying any hints and without using SET FORCEPLAN.

Что случилось? Почему это, казалось бы, безобидное изменение в запросе приводит к его сбою? Чтобы найти ответ, давайте запустим запрос без подсказки HASH JOIN и посмотрим, что произойдёт с его планом:

SELECT *

FROM T1 INNER JOIN T2 ON T1.A = T2.A

WHERE T1.A = 0

  |--Nested Loops(Inner Join)

       |--Table Scan(OBJECT:([T2]), WHERE:([T2].[A]=(0)) )

       |--Table Scan(OBJECT:([T1]), WHERE:([T1].[A]=(0)))

В этом плане есть две примечательные вещи:

  • Во-первых, план включает предикат "T2.A = 0". Хотя мы не указали этот предикат в исходном запросе, SQL Server добавляет его на основе анализа предикатов, которые мы указали. Это позволяет предварительно отфильтровывать строки просмотра по T2.
  • Во-вторых, исходного предиката эквисоединения "T1.A = T2.A" в этом плане нигде нет. Этот предикат оказывается избыточный, когда и так уже есть "T1.A = 0" и производный от них предикат "T2.A = 0", поэтому SQL Server его отбрасывает. Обычно отказ от избыточного предиката  это хорошо. К тому времени как строки из просматриваемой таблицы достигают соединения, уже нет причин использовать этот предикат, это было бы пустой тратой ресурсов. К сожалению, в этом случае отброшенный предикат оказывается единственным предикатом эквисоединения, а для хэш-соединений (и соединений слиянием) требуется хотя-бы один предикат эквисоединения. Поэтому запрос с подсказкой HASH JOIN завершится ошибкой.

Обратите внимание, что с точки зрения производительности потеря альтернатив для хэш-соединения и соединения слиянием в приведенном выше плане проблемой не станет, поскольку в любом случае запрос является перекрестным соединением, и все строки из T1 будут соединены со всеми строками из T2.

Комментариев нет:

Отправить комментарий