恆定掃描加入
在準備我之前的恆定掃描問題時,我
VALUES
以各種方式進行了試驗,並遇到了關於加入的事情,VALUES
這對我來說看起來很奇怪。設置很簡單
CREATE TABLE #data ([Id] int); INSERT INTO #data VALUES (101), (103);
然後有一個查詢
DECLARE @id1 int = 101, @id2 int = 102; SELECT * FROM (VALUES (@id1), (@id2)) p([Id]) FULL HASH JOIN #data d ON d.[Id] = p.[Id];
它沒有什麼特別之處。如果你執行它,它會工作並產生結果。這是它的執行計劃
VALUES
但是從中刪除一行SELECT * FROM (VALUES (@id1)) p([Id]) FULL HASH JOIN #data d ON d.[Id] = p.[Id];
導致優化器失敗
消息 8622,級別 16,狀態 1,第 1 行
查詢處理器無法生成查詢計劃…
為什麼?有沒有辦法(除了將參數放入臨時表)使用雜湊算法使其工作?
**注意:**這不是真正的設備,用於研究優化器的行為和能力。
上面的例子在
Microsoft SQL Server 2017 (RTM-CU15-GDR) (KB4505225) - 14.0.3192.2 (X64)
為什麼?
簡而言之。因為
HASH
提示射擊優化器的腿和優化器本身射擊到另一個。被兩個優化器擊中不能越過終點線。為了更好地說明發生了什麼,讓我們重寫有問題的查詢以連接兩個
VALUES
並改用合併算法DECLARE @id1 int = 101, @id3 int = 103; SELECT * FROM (VALUES (@id1)) p([Id]) FULL MERGE JOIN (VALUES (@id1), (@id3)) d([Id]) ON d.[Id] = p.[Id];
這個查詢的執行計劃很簡單。有兩個恆定掃描輸入的合併連接運算符。
這兩個常量掃描與優化器不同。
一個表示單行輸入的列名以 為前綴
Expr
,而另一個表示多行輸入的列名以 為前綴Union
。在 Merge Join 謂詞中訪問來自多行常量掃描的數據是一種“按引用[Union1001]
” (@id1``[Expr1000]
這種“按引用”→“按值”替換是在早期優化階段執行的標量映射的結果。
可以看到(使用跟踪標誌 8606)在輸入樹連接謂詞中是
[Union1001] = [Expr1000]
*** 輸入樹:*** ... LogOp_FullOuterJoin ... ScaOp_Comp x_cmpEq ScaOp_Identifier COL:Union1001 **ScaOp_Identifier COL:Expr1000** ...
但是在簡化樹中它變成了
[Union1001] = @id1
*** 簡化樹:*** LogOp_FullOuterJoin ... ScaOp_Comp x_cmpEq ScaOp_Identifier COL:Union1001 **ScaOp_Identifier COL:@id1**
標量映射是投影拉動邏輯的一部分,實際上是在進入簡化階段之前執行的。
之前可能確實注意到,Merge Join 節點只有殘差謂詞,沒有連接相等謂詞。這是因為連接相等謂詞已被標量映射消除。is 相等謂詞,
[Union1001] = @id1
但不能用作連接相等謂詞。為此,它必須從兩個輸入中引用列,但@id1
它是可變的而不是列。因此,
ON d.[Id] = p.[Id]
最初是 equijoin,查詢轉換為 non-equijoin(這是特殊情況,因此,順便說一下,優化器沒有在 Merge Join 下面為未排序的常量掃描輸入引入排序)。幸運的是,在合併算法的情況下,優化器有這樣的非等連接替代方案。在使用雜湊算法的情況下,不存在非等連接替代方案,因此連接等式謂詞消除會導致優化器稍後失敗。
有沒有辦法(除了將參數放入臨時表)使用雜湊算法使其工作?
沒有阻止標量映射的跟踪標誌(*),查詢槓桿、會話級別和啟動都沒有。並且沒有可以關閉的優化規則來防止它,因為它不是由規則執行的。
我只能通過在
COptExpr::PexprMapScalar
常式中設置斷點來執行有問題的查詢
eax
並在呼叫後修改寄存器的值ScaOp_Identifier::ClassNo
以使SQL Server認為第二個操作數ScaOp_Comp
不是標識符。這是問題中發布的問題查詢的簡化樹
*** 簡化樹:*** LogOp_FullOuterJoin LogOp_ConstTableGet (1) COL: Expr1000 ScaOp_Identifier COL:@id1 LogOp_Get TBL:#data(別名 TBL:d) ScaOp_Comp x_cmpEq ScaOp_Identifier QCOL:[d].Id **ScaOp_Identifier COL:Expr1000** *******************
這是它的計劃。
它實際上沒有什麼意義,因為獲得的計劃成本是 0.0210675 個單位,而在沒有
HASH
提示的情況下執行查詢會導致執行計劃與 Merge Join (注意沒有再次在 Merge Join 下面排序)成本 0.0088948 單位。
(*)可能存在跟踪標誌的組合。我認為不是,但我沒有探索所有程式碼路徑,所以我不確定。