SQL Server에서 다음과 같은 경우 LOOP JOIN을 강제 실행해야합니까?


15

일반적으로 모든 표준 이유로 조인 힌트를 사용하지 않는 것이 좋습니다. 그러나 최근에는 거의 항상 강제 루프 조인이 더 나은 성능을 발휘하는 패턴을 발견했습니다. 사실, 나는 그것을 사용하기 시작하고 너무 많이 추천하여 내가 뭔가 빠진 것이 아닌지 확인하기 위해 두 번째 의견을 얻고 싶었습니다. 다음은 대표적인 시나리오입니다 (예제를 생성하기위한 매우 구체적인 코드가 끝났습니다).

--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 계획 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

*/

답변:


13

SampleTable은 약 36MB를 사용하는 4714 페이지에 포함되어 있습니다. 사례 1은 그것들을 모두 스캔하기 때문에 우리는 4714 개의 읽기를 얻습니다. 또한 CPU를 많이 사용하고 궁극적으로 시간을 비례 적으로 늘리는 백만 해시를 수행해야합니다. 사례 1에서 시간을 늘리는 것은이 모든 해싱입니다.

해시 조인 (해시 테이블 작성, 차단 작업이기도 함)에 대한 시작 비용이 있지만 해시 조인은 궁극적 으로 SQL Server에서 지원하는 세 가지 물리적 조인 유형 중 이론적으로 행당 비용이 가장 낮 습니다. IO와 CPU의 조건. 해시 조인은 실제로 비교적 작은 빌드 입력과 큰 프로브 입력으로 자체적으로 발생합니다. 즉, 모든 시나리오에서 물리적 조인 유형이 '더 나은'것은 아닙니다.

이제 사례 2를 고려하십시오. 해싱을 수행하지 않고 대신 50000 개의 개별 탐색을 수행하고 있습니다. 그러나 판독 값이 상대적으로 얼마나 비쌉니까? 물리적 인 읽기라면 꽤 비쌀 수 있습니다. 그러나 1) 주어진 페이지의 첫 번째 읽기 만 물리적 일 수 있으며 2) 그럼에도 불구하고 사례 1은 모든 페이지를 공격 할 수 있으므로 동일하거나 더 나쁜 문제가 있음을 명심하십시오.

각 탐색은 b- 트리를 루트로 탐색해야하며 단일 해시 프로브에 비해 계산 비용이 많이 듭니다. 또한 중첩 루프 조인의 내부에 대한 일반적인 IO 패턴은 해시 조인에 대한 프로브 측 스캔 입력의 순차적 액세스 패턴과 비교하여 임의적입니다. 기본 물리적 IO 하위 시스템에 따라 순차 읽기가 임의 읽기보다 빠를 수 있습니다. 또한 SQL Server 미리 읽기 메커니즘은 순차적 IO에서 더 잘 작동하여 더 큰 읽기를 발행합니다.

따라서 두 경우 모두 각 페이지에 적어도 한 번 액세스해야한다는 사실을 고려할 때 메모리에 대한 백만 해시 또는 약 155000 회 읽기 중 어느 것이 더 빠른지 궁금합니다. 내 테스트는 후자를 말하는 것처럼 보이지만 SQL Server는 지속적으로 전자를 선택합니다.

SQL Server 쿼리 최적화 프로그램은 여러 가지 가정을합니다. 하나는 쿼리에 의해 만들어진 페이지에 대한 첫 번째 액세스는 물리적 IO ( '콜드 캐시 가정')를 초래한다는 것입니다. 동일한 쿼리에 의해 이미 메모리에서 읽은 페이지에서 나중에 읽을 가능성이 모델링되지만, 이는 단순한 추측에 지나지 않습니다.

옵티 마이저 모델이 이런 식으로 작동하는 이유는 일반적으로 최악의 경우에 최적화하는 것이 더 낫기 때문입니다 (물리적 IO가 필요함). 병렬 처리와 메모리의 실행으로 많은 단점을 해결할 수 있습니다. 모든 데이터가 메모리에 있다고 가정하면 옵티마이 저가 생성하는 쿼리 계획은 해당 가정이 유효하지 않은 것으로 판명되면 성능이 매우 저하 될 수 있습니다.

콜드 캐시 가정을 사용하여 생성 된 계획은 웜 캐시가 대신 가정 된 것처럼 성능이 좋지 않을 수 있지만 최악의 성능은 일반적으로 우수합니다.

테스트에서 이러한 종류의 결과가 표시 될 때이 LOOP JOIN 힌트를 계속 사용해야합니까, 아니면 분석에서 누락 된 부분이 있습니까? SQL Server의 옵티 마이저에 반대하는 것이 주저하지만 이러한 경우보다 훨씬 빨리 해시 조인을 사용하는 것으로 전환됩니다.

두 가지 이유로이 작업을 수행하는 데 매우주의해야합니다. 먼저, 힌트는 자동으로 물리적는도 지정했던 것처럼 쿼리 (의 작성 순서와 일치하기 위해 가입 강제 가입 OPTION (FORCE ORDER)이 심각 최적화에 사용할 수있는 대안을 제한합니다., 항상 당신이 원하는하지 않을 수 있습니다. OPTION (LOOP JOIN)힘을 중첩 루프를 쿼리에 조인하지만 작성된 조인 순서를 적용하지는 않습니다.

둘째, 데이터 세트 크기가 작고 대부분의 논리적 읽기는 캐시에서 발생한다고 가정합니다. 이러한 가정이 유효하지 않은 경우 (시간이 지남에 따라) 성능이 저하됩니다. 내장 된 쿼리 최적화 프로그램은 변화하는 환경에 잘 대처합니다. 그 자유를 제거하는 것은 당신이 열심히 생각해야하는 것입니다.

전반적으로 루프 조인을 강제 해야하는 강력한 이유 가 없다면 그것을 피할 것입니다. 기본 계획은 일반적으로 최적에 가깝고, 변화하는 환경에보다 탄력적 인 경향이 있습니다.


폴 감사합니다. 탁월한 상세 분석. 내가 한 몇 가지 추가 테스트를 기반으로, 임시 테이블의 크기가 5K에서 100K 사이 일 때 최적화 프로그램의 교육 된 추측 이이 특정 예에서 일관되게 꺼져 있다고 생각합니다. 우리의 요구 사항이 임시 테이블이 50K 미만임을 보장한다는 사실을 감안할 때 안전합니다. 궁금합니다.이 점을 아는 조인 힌트는 여전히 피 하시겠습니까?
JohnnyM

1
@JohnnyM 힌트는 이유가 있습니다. 적절한 이유가있는 곳에서 사용하는 것이 좋습니다. 즉 , 암시 적으로 인해 조인 힌트를 거의 사용하지 않습니다 FORCE ORDER. 이상한 경우에 나는 조인 힌트를 사용하며, 종종 OPTION (FORCE ORDER)이유를 설명하기 위해 주석을 추가 합니다.
Paul White 9

0

백만 행 테이블에 대해 조인 된 50,000 행은 인덱스가없는 테이블에 많은 것으로 보입니다.

이 경우 실제로 해결하려고하는 문제와 너무 분리되어 있기 때문에이 경우 어떻게 해야하는지 정확하게 알려주기가 어렵습니다. 확실히 많은 양의 행이있는 인덱싱되지 않은 많은 임시 테이블에 대해 조인하는 코드의 일반적인 패턴이 아니기를 바랍니다.

그것이 말하는 것에 대한 예를 들어, 왜 #Driver에 색인을 두지 않겠습니까? D.ID는 정말 독특합니까? 그렇다면 이는 EXISTS 문과 의미가 동일합니다. 최소한 SQL Server에 S의 중복 값 D에 대한 검색을 계속하고 싶지 않다는 것을 알립니다.

SELECT S.*
INTO #Results
FROM SampleTable S
WHERE EXISTS (SELECT * #Driver D WHERE S.ID = D.ID);

요컨대,이 패턴에서는 LOOP 힌트를 사용하지 않습니다. 나는이 패턴을 사용하지 않을 것이다. 가능한 경우 우선 순위에 따라 다음 중 하나를 수행합니다.

  • 가능하면 #Driver에 임시 테이블 대신 CTE를 사용하십시오.
  • 고유 한 경우 ID의 #Driver에서 고유 한 비 클러스터형 인덱스를 사용하십시오 (#Driver를 사용하는 유일한 시간이고 테이블 자체의 데이터를 원하지 않는 경우-실제로 해당 테이블의 데이터가 필요한 경우) 클러스터 된 인덱스로 만들 수도 있습니다.)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.