Sql-Server
當建構端為空時,SQL Server 為什麼/何時評估內部散列連接的探測端?
設置
DROP TABLE IF EXISTS #EmptyTable, #BigTable CREATE TABLE #EmptyTable(A int); CREATE TABLE #BigTable(A int); INSERT INTO #BigTable SELECT TOP 10000000 CRYPT_GEN_RANDOM(3) FROM sys.all_objects o1, sys.all_objects o2, sys.all_objects o3;
詢問
WITH agg AS (SELECT DISTINCT a FROM #BigTable) SELECT * FROM #EmptyTable E INNER HASH JOIN agg B ON B.A = E.A;
執行計劃
問題
這是我今天之前沒有註意到的現象的簡化再現。我對內部雜湊連接的期望是,如果建構輸入為空,則不應執行探測端,因為連接不能返回任何行。上面的範例與此相矛盾,並從表中讀取了 1000 萬行。這使查詢的執行時間增加了 2.196 秒(99.9%)。
額外的觀察
OPTION (MAXDOP 1)
執行計劃不會從#BigTable
.ActualExecutions
適用0
於散列連接內部的所有運算符。- 對於查詢
SELECT * FROM #EmptyTable E INNER HASH JOIN #BigTable B ON B.A = E.A
- 我得到了一個並行計劃,散列連接內部的掃描運算符確實具有ActualExecutions
DOP,但仍然沒有讀取任何行。該計劃沒有重新分區流運算符(或聚合)問題
這裡發生了什麼?為什麼原計劃會出現問題,而其他情況卻沒有?
當建構為空時不執行連接的探測端是一種優化。當探測端有子分支時,即存在交換運算符時,它不適用於並行行模式雜湊連接。
許多年前,Adam Machanic 在現已解散的 Connect 回饋網站上發表了類似的報告。該場景是探針端的一個啟動過濾器,它意外地執行了它的子操作符。微軟的回答是,引擎需要保證某些結構已初始化,而唯一明智的強制執行方法是確保打開探測端操作符。
我自己對細節的回憶是,不初始化子樹會導致難以修復的並行時序錯誤。確保子分支啟動是解決這些問題的方法。
批處理模式雜湊聯接沒有這種副作用,因為管理執行緒的方式不同。
在您的特定情況下,效果更明顯,因為雜湊聚合是阻塞的;它在迭代器的 Open() 呼叫期間消耗其全部輸入。當探測端只有流式操作符時,性能影響通常會更有限,這取決於將第一行返回到雜湊連接的探測端需要多少工作。