通常、すべての標準的な理由から、結合ヒントを使用しないことをお勧めします。しかし、最近、パフォーマンスを向上させるために強制ループ結合をほぼ常に見つけるパターンを見つけました。実際、私はそれを使い始め、推奨しすぎているので、何かを見逃していないことを確認するためにセカンドオピニオンを得たいと思いました。代表的なシナリオを次に示します(例を生成するための非常に具体的なコードは最後にあります):
--Case 1: NO HINT
SELECT S.*
INTO #Results
FROM #Driver AS D
JOIN SampleTable AS S ON S.ID = D.ID
--Case 2: LOOP JOIN HINT
SELECT S.*
INTO #Results
FROM #Driver AS D
INNER LOOP JOIN SampleTable AS S ON S.ID = D.ID
SampleTableには100万行があり、そのPKはIDです。
一時テーブル#Driverには、1つの列、ID、インデックスなし、5万行しかありません。
私が一貫して見つけることは次のとおりです。
ケース1:NOヒント
SampleTableを上のインデックススキャン
ハッシュ結合
より高い期間を(平均333ms)
高いCPU(平均331ms)
論理低は(4714)を読み込み
ケース2:
SampleTable
ループ結合のLOOP JOIN HINT インデックスシーク
継続時間の短縮(平均204ミリ秒、39%減少)
CPUの減少(平均206、38 %減少)
はるかに高い論理読み取り(160015、34X増加)
最初は、2番目のケースの非常に高い読み取り値は、読み取り値を低くすることがパフォーマンスの適切な尺度と見なされることが多いため、少し怖いものでした。しかし、実際に何が起こっているのかを考えるほど、それは私には関係ありません。私の考えは次のとおりです。
SampleTableは4714ページに含まれ、約36MBを消費します。ケース1ですべてがスキャンされるため、4714の読み取りが行われます。さらに、100万回のハッシュを実行する必要がありますが、これはCPUを集中的に使用し、最終的に比例して時間を増加させます。ケース1でタイムアップするのは、このすべてのハッシュです。
ここで、ケース2について考えてみましょう。ハッシュを実行していませんが、代わりに50000の個別シークを実行しています。これが読み取りを促進しています。しかし、読み取りは比較してどれくらい高価ですか?それらが物理的な読み取りである場合、かなり高価になる可能性があると言うかもしれません。ただし、1)特定のページの最初の読み取りのみが物理的である可能性があり、2)たとえそうであっても、ケース1はすべてのページにヒットすることが保証されているため、同じまたはより悪い問題を抱えることに留意してください。
両方のケースが少なくとも1回は各ページにアクセスする必要があるという事実を考慮すると、どちらが高速であるか、100万回のハッシュ、またはメモリに対する約155000回の読み取りのどちらの問題なのでしょうか?私のテストでは後者のように見えますが、SQL Serverは一貫して前者を選択します。
質問
それでは私の質問に戻りましょう。テストでこの種の結果が表示されたときに、このLOOP JOINヒントを強制し続けるべきですか、それとも分析で何かが欠けていますか?私はSQL Serverのオプティマイザーに反対するのをためらっていますが、このような場合よりもはるかに早くハッシュ結合の使用に切り替えるように感じます。
更新2014-04-28
さらにテストを行い、上記の結果(2 CPUのVMで)が他の環境で複製できないことを発見しました(8と12 CPUの2つの異なる物理マシンで試しました)。オプティマイザーは、後者の場合、そのような顕著な問題がなくなるまでずっと良くなりました。振り返ってみれば明らかなように、学んだ教訓は、環境がオプティマイザーの動作に大きく影響する可能性があるということです。
実行計画
実行計画ケース1 実行計画ケース2
サンプルケースを生成するコード
------------------------------------------------------------
-- 1. Create SampleTable with 1,000,000 rows
------------------------------------------------------------
CREATE TABLE SampleTable
(
ID INT NOT NULL PRIMARY KEY CLUSTERED
, Number1 INT NOT NULL
, Number2 INT NOT NULL
, Number3 INT NOT NULL
, Number4 INT NOT NULL
, Number5 INT NOT NULL
)
--Add 1 million rows
;WITH
Cte0 AS (SELECT 1 AS C UNION ALL SELECT 1), --2 rows
Cte1 AS (SELECT 1 AS C FROM Cte0 AS A, Cte0 AS B),--4 rows
Cte2 AS (SELECT 1 AS C FROM Cte1 AS A ,Cte1 AS B),--16 rows
Cte3 AS (SELECT 1 AS C FROM Cte2 AS A ,Cte2 AS B),--256 rows
Cte4 AS (SELECT 1 AS C FROM Cte3 AS A ,Cte3 AS B),--65536 rows
Cte5 AS (SELECT 1 AS C FROM Cte4 AS A ,Cte2 AS B),--1048576 rows
FinalCte AS (SELECT ROW_NUMBER() OVER (ORDER BY C) AS Number FROM Cte5)
INSERT INTO SampleTable
SELECT Number, Number, Number, Number, Number, Number
FROM FinalCte
WHERE Number <= 1000000
------------------------------------------------------------
-- Create 2 SPs that join from #Driver to SampleTable.
------------------------------------------------------------
GO
IF OBJECT_ID('JoinTest_NoHint') IS NOT NULL DROP PROCEDURE JoinTest_NoHint
GO
CREATE PROC JoinTest_NoHint
AS
SELECT S.*
INTO #Results
FROM #Driver AS D
JOIN SampleTable AS S ON S.ID = D.ID
GO
IF OBJECT_ID('JoinTest_LoopHint') IS NOT NULL DROP PROCEDURE JoinTest_LoopHint
GO
CREATE PROC JoinTest_LoopHint
AS
SELECT S.*
INTO #Results
FROM #Driver AS D
INNER LOOP JOIN SampleTable AS S ON S.ID = D.ID
GO
------------------------------------------------------------
-- Create driver table with 50K rows
------------------------------------------------------------
GO
IF OBJECT_ID('tempdb..#Driver') IS NOT NULL DROP TABLE #Driver
SELECT ID
INTO #Driver
FROM SampleTable
WHERE ID % 20 = 0
------------------------------------------------------------
-- Run each test and run Profiler
------------------------------------------------------------
GO
/*Reg*/ EXEC JoinTest_NoHint
GO
/*Loop*/ EXEC JoinTest_LoopHint
------------------------------------------------------------
-- Results
------------------------------------------------------------
/*
Duration CPU Reads TextData
315 313 4714 /*Reg*/ EXEC JoinTest_NoHint
309 296 4713 /*Reg*/ EXEC JoinTest_NoHint
327 329 4713 /*Reg*/ EXEC JoinTest_NoHint
398 406 4715 /*Reg*/ EXEC JoinTest_NoHint
316 312 4714 /*Reg*/ EXEC JoinTest_NoHint
217 219 160017 /*Loop*/ EXEC JoinTest_LoopHint
211 219 160014 /*Loop*/ EXEC JoinTest_LoopHint
217 219 160013 /*Loop*/ EXEC JoinTest_LoopHint
190 188 160013 /*Loop*/ EXEC JoinTest_LoopHint
187 187 160015 /*Loop*/ EXEC JoinTest_LoopHint
*/