필터링 된 부분이 WHERE가 아닌 JOIN에있을 때만 필터링 된 인덱스


10

아래에서 필터링 된 인덱스를 만들었지 만 2 개의 쿼리를 더 아래로 실행하면이 인덱스는 where 절이 아닌 JOIN에 END_DTTM가있는 첫 번째 예제에서만 탐색에 사용됩니다 (쿼리의 유일한 차이점) . 왜 이런 일이 발생했는지 설명 할 수 있습니까?

인덱스 생성

CREATE NONCLUSTERED INDEX [ix_PATIENT_LIST_BESPOKE_LIST_ID_includes] ON [dbo].[PATIENT_LIST_BESPOKE] 
(
    [LIST_ID] ASC,
    [END_DTTM] ASC
)
WHERE ([END_DTTM] IS NULL)

쿼리

DECLARE @LIST_ID INT = 3655

--This one seeks on the index

SELECT  
    PATIENT_LISTS.LIST_ID
FROM    
    DBO.PATIENT_LISTS
    LEFT JOIN DBO.PATIENT_LIST_BESPOKE ON PATIENT_LISTS.LIST_ID = PATIENT_LIST_BESPOKE.LIST_ID  
                                      AND PATIENT_LIST_BESPOKE.END_DTTM IS NULL
WHERE
    PATIENT_LISTS.LIST_ID = @LIST_ID

--This one scans on the index

SELECT  
    PATIENT_LISTS.LIST_ID
FROM    
    DBO.PATIENT_LISTS
    LEFT JOIN DBO.PATIENT_LIST_BESPOKE ON PATIENT_LISTS.LIST_ID = PATIENT_LIST_BESPOKE.LIST_ID  
WHERE   
    PATIENT_LISTS.LIST_ID = @LIST_ID AND
    PATIENT_LIST_BESPOKE.END_DTTM IS NULL   

답변:


12

옵티마이 저가 술어를 인덱스 (필터링되거나 다르게)와 일치 시키려면, 술어는 논리 쿼리 트리에서 Get 조작에 인접하여 나타나야합니다. 이를 용이하게하기 위해, 술어는 일반적으로 최적화가 시작되기 전에 논리 트리의 잎에 가능한 한 가깝게 푸시됩니다.

단순화하기 위해 실제 인덱스 전략 구현은 다음을 수행합니다.

Predicate + Logical Get -> Physical Get (using Index)

관심있는 쿼리는 외부 조인 위의 술어로 시작합니다.

Predicate on T2 --+-- LOJ -- Get (T1)
                       |
                       +---- Get (T2)

술어가 Get에 인접하지 않기 때문에이 모양은 인덱스 전략 규칙과 일치하지 않습니다. 따라서 응답의 첫 번째 부분은 술어를 외부 조인을 통과하여 푸시 할 수 없으면 필터링 된 인덱스 일치가 실패한다는 것입니다.

두 번째 부분은 변환이 그렇게 거의 유효하지 않기 때문에 옵티마이 저가 보존 측의 외부 조인을지나 술어를 이동하는 데 필요한 탐색 규칙을 포함하지 않는다는 것입니다. 가장 유용한 규칙 만 구현되는 것이 옵티마이 저의 일반적인 기능입니다.

결과적으로이 경우 필터링 된 인덱스 일치가 실패합니다. 분명히 다시 작성은 인용 한 매우 구체적인 경우에 유효 합니다 (두 번째 쿼리).

시맨틱이 다른 첫 번째 쿼리 양식의 경우, 술어는 시작에서 조인과 연관되며 술어 푸시 다운 논리 는 외부 조인을 지나서 이동할 필요가 없기 때문에이를 짧은 거리로 가져올 수 있습니다. 위에서 설명했다.

배경 및 추가 정보 :


9

하나는 조인 전에 필터링 할 수 있고 다른 하나는 필터링 할 수 있기 때문에 의미 적으로 동일한 쿼리 가 아닙니다 . 더 간단한 예를 들어 설명하겠습니다.

CREATE TABLE dbo.Lefty(LeftyID INT PRIMARY KEY);

CREATE TABLE dbo.Righty(LeftyID INT, SomeList INT);

INSERT dbo.Lefty(LeftyID) VALUES(1),(2),(3);

INSERT dbo.Righty(LeftyID, SomeList) VALUES(1,1),(1,NULL),(2,2);

쿼리 1은 세 행을 모두 반환합니다.

SELECT l.LeftyID, r.SomeList
FROM dbo.Lefty AS l
LEFT OUTER JOIN dbo.Righty AS r
ON l.LeftyID = r.LeftyID
AND r.SomeList IS NULL;

그러나 쿼리 2는 LeftyID 2를 제외합니다.

SELECT l.LeftyID, r.SomeList
FROM dbo.Lefty AS l
LEFT OUTER JOIN dbo.Righty AS r
ON l.LeftyID = r.LeftyID
WHERE r.SomeList IS NULL;

SQLfiddle 증거

안티-세미 결합을 수행하려는 경우 테스트 된 컬럼 은 널 입력 가능하지 않아야 합니다. ONNER WHERE 사이의 기준 이동은 INNER 조인 만 처리 할 때 논리적 차이가 없지만 OUTER에서는 큰 차이가 있습니다. 또한 필터링 된 인덱스를 사용할 수 있는지 여부보다 결과가 정확하도록주의를 기울여야합니다.


답변에 감사하지만 쿼리가 동일하다고 주장하지는 않지만 하나의 쿼리가 필터링 된 인덱스를 사용하고 다른 쿼리는 왜 사용하지 않는지 묻습니다.
chris

@ chris 인덱스 힌트로 해당 인덱스를 강제하려고 했습니까? 나는 실제적인 사후 계획과 그 힌트가 있거나없는 것을 비교하는 것이 궁금합니다. 필자는 옵티마이 저가 반 세미 조인을 수행한다고 생각할 때 해당 인덱스를 무시하고 있음이 분명합니다 (이 경우 널 입력 가능 열이 사용될 것으로 예상하지 않기 때문에). 필터링 된 인덱스에있는 것보다 왼쪽에서 더 많은 행이있을 수 있다는 원가 계산 또는 작업 순서 또는 일부 기본 지식과 관련이 있습니다. 계획을 보면 도움이 될 수 있습니다.
Aaron Bertrand

3

두 쿼리는 의미와 결과가 다릅니다. 다음은 다시 작성하는 것이므로 두 쿼리가 수행하는 작업이 더 분명합니다.

-- 1st query
SELECT  
    a.LIST_ID
FROM    
      ( SELECT LIST_ID 
        FROM   DBO.PATIENT_LISTS
        WHERE  LIST_ID = @LIST_ID
      ) AS a
    LEFT JOIN  
      ( SELECT LIST_ID                    -- the filtered index
        FROM   DBO.PATIENT_LIST_BESPOKE   -- can be used
        WHERE  END_DTTM IS NULL           -- for the subquery
      ) AS b
    ON  a.LIST_ID = b.LIST_ID ;           -- and the join

둘째 :

-- 2nd query
SELECT  
    a.LIST_ID
FROM    
      ( SELECT LIST_ID 
        FROM   DBO.PATIENT_LISTS
        WHERE  LIST_ID = @LIST_ID
      ) AS a
    JOIN  
      ( SELECT LIST_ID                    -- the filtered index
        FROM   DBO.PATIENT_LIST_BESPOKE   -- can be used
        WHERE  END_DTTM IS NULL           -- for the subquery
      ) AS b
    ON  a.LIST_ID = b.LIST_ID             -- and the join

UNION ALL

SELECT  
    a.LIST_ID
FROM    
      ( SELECT LIST_ID 
        FROM   DBO.PATIENT_LISTS
        WHERE  LIST_ID = @LIST_ID
      ) AS a
WHERE NOT EXISTS  
      ( SELECT *
        FROM   DBO.PATIENT_LIST_BESPOKE AS b
        WHERE  a.LIST_ID = b.LIST_ID         -- but not for this
      ) ;

2nq 쿼리의 두 번째 부분에서는 필터링 된 인덱스를 사용할 수 없다는 것이 분명합니다.


이러한 쿼리와 관련 LIST_ID하여 첫 번째 테이블 에는 4 가지 유형의 값이 있습니다.

  • 두 번째 테이블에서 매칭이 행 (A) 값을 가진 모든 END_DTTM IS NULL.

  • 함께 두 번째 테이블의 행 일치 한 (b)의 값 END_DTTM IS NULL과 함께 END_DTTM IS NOT NULL.

  • (c) 두 번째 테이블에서 매칭되는 열이 값을 모든 END_DTTM IS NOT NULL.

  • (d) 두 번째 테이블에 일치하는 행이없는 값.

이제 첫 번째 쿼리는 (a) 및 (b) 유형의 모든 값을 여러 번 반환합니다 (두 번째 테이블에 일치하는 행이있는 횟수 END_DTTM IS NULL) 및 (c) 및 (d) 유형의 모든 행이 정확히 한 번 ( 그것은 외부 조인의 일치하지 않는 부분입니다).

두 번째 쿼리는 (a) 및 (b) 유형의 모든 값을 여러 번 (두 번째 테이블에 일치하는 행이있는 수 END_DTTM IS NULL) 및 (d) 유형의 모든 행을 정확히 한 번만 반환합니다 .
그것은 것입니다 하지 (가) 두 번째 테이블에 일치하는 행을 찾을 가입하기 때문에 유형 (C)의 값을 반환 (그러나 이들은 것입니다 END_DTTM IS NOT NULL) 그리고 그들은 이후에 의해 제거 될 것이다 WHERE절.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.