
Автор: 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.
Комментариев нет:
Отправить комментарий