일반적으로 모든 표준 이유로 조인 힌트를 사용하지 않는 것이 좋습니다. 그러나 최근에는 거의 항상 강제 루프 조인이 더 나은 성능을 발휘하는 패턴을 발견했습니다. 사실, 나는 그것을 사용하기 시작하고 너무 많이 추천하여 내가 뭔가 빠진 것이 아닌지 확인하기 위해 두 번째 의견을 얻고 싶었습니다. 다음은 대표적인 시나리오입니다 (예제를 생성하기위한 매우 구체적인 코드가 끝났습니다).
--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에는 백만 개의 행이 있으며 PK는 ID입니다.
임시 테이블 #Driver에는 하나의 열, ID, 인덱스 없음 및 50K 행만 있습니다.
내가 지속적으로 찾는 것은 다음과 같습니다.
사례 1 :
SampleTable에 대한 힌트 인덱스 스캔 없음
해시 조인
더 높은 지속 시간 (avg 333ms)
더 높은 CPU (avg 331ms)
더 낮은 논리적 읽기 (4714)
사례 2 : LOOP JOIN HINT
인덱스가 SampleTable
루프 조인 에서 탐색
지속 시간 감소 (avg 204ms, 39 % 감소)
CPU 감소 (avg 206, 38 % 감소)
훨씬 더 높은 논리적 읽기 (160015, 34X 이상)
처음에는 두 번째 경우에 대한 더 높은 판독 값이 저를 두렵게했습니다. 판독 값을 낮추는 것이 종종 적절한 성능 측정 기준으로 간주되기 때문입니다. 그러나 실제로 일어나는 일에 대해 더 많이 생각할수록 걱정하지 않습니다. 내 생각은 다음과 같습니다.
SampleTable은 약 36MB를 사용하는 4714 페이지에 포함되어 있습니다. 사례 1은 그것들을 모두 스캔하기 때문에 우리는 4714 개의 읽기를 얻습니다. 또한 CPU를 많이 사용하고 궁극적으로 시간을 비례 적으로 늘리는 백만 해시를 수행해야합니다. 사례 1에서 시간을 늘리는 것은이 모든 해싱입니다.
이제 사례 2를 고려하십시오. 해싱을 수행하지 않고 대신 50000 개의 개별 탐색을 수행하고 있습니다. 그러나 판독 값이 비교적으로 얼마나 비쌉니까? 물리적 인 읽기라면 꽤 비쌀 수 있습니다. 그러나 1) 주어진 페이지의 첫 번째 읽기 만 물리적 일 수 있으며 2) 그럼에도 불구하고 사례 1은 모든 페이지를 공격 할 수 있으므로 동일하거나 더 나쁜 문제가 있음을 명심하십시오.
따라서 두 경우 모두 각 페이지에 적어도 한 번 액세스해야한다는 사실을 고려할 때 메모리에 대한 백만 해시 또는 약 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
*/