_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がありますが、それでも行は読み取られません。このプランには、再パーティションストリームオペレーター(または集計)がありません何が起きてる?元のプランに問題があり、他のケースにはないのはなぜですか?
ビルドが空のときに結合のプローブ側を実行しないことは最適化です。 並列行モードハッシュ結合には使用できません。プローブ側に子ブランチがある場合、つまり交換演算子がある場合です。
何年も前に、現在は廃止されたConnectフィードバックサイトで、Adam Machanicによる同様のレポートがありました。このシナリオは、プローブ側の起動フィルターであり、子オペレーターを予期せず実行していました。 Microsoftからの回答は、特定の構造が初期化されていることの保証がエンジンに必要であり、それを実施する唯一の正しい方法は、プローブ側のオペレーターが開かれていることを確認することでした。
詳細についての私自身の思い出は、サブツリーを初期化しないと、修正が困難な並列タイミングのバグが発生したことです。子ブランチの起動を確実にすることは、これらの問題の回避策でした。
スレッドの管理方法が異なるため、バッチモードのハッシュ結合にはこの副作用はありません。
特定のケースでは、ハッシュ集約がブロックしているため、効果はより顕著になります。イテレータのOpen()呼び出し中に入力全体を消費します。プローブ側にストリーミングオペレーターしかない場合、ハッシュ結合のプローブ側に最初の行を返すために必要な作業量に応じて、パフォーマンスへの影響はより制限されることがよくあります。
答えではありませんが、ハッシュ結合が強制されない場合、そのクエリは計画としてハッシュ結合を取得しません。回避策は、テーブルに行が存在する場合はビット変数を1に設定し、存在しない場合は0に設定し、#Emptytableの代わりに使用する(Select * from #Emptytable where @bit = 1)
そして、オプションrecompileを最後に追加すると、実行は行われません。
強制が使用されておらず、強制が必要な場合に回避策が存在する場合、この状態は発生しないはずだと思います。