JOIN 절에서 OR 사용시 이상한 쿼리 계획-테이블의 모든 행에 대한 지속적인 스캔


10

JOON 절에서 OR를 사용하는 것보다 UNIONing 두 결과 집합이 더 나은 이유를 보여주기 위해 샘플 쿼리 계획을 작성하려고합니다. 내가 작성한 쿼리 계획으로 인해 문제가 발생했습니다. Users.Reputation에서 비 클러스터형 인덱스가있는 StackOverflow 데이터베이스를 사용하고 있습니다.

쿼리 계획 이미지 쿼리는

CREATE NONCLUSTERED INDEX IX_NC_REPUTATION ON dbo.USERS(Reputation)
SELECT DISTINCT Users.Id
FROM dbo.Users
INNER JOIN dbo.Posts  
    ON Users.Id = Posts.OwnerUserId
    OR Users.Id = Posts.LastEditorUserId
WHERE Users.Reputation = 5

쿼리 계획은 https://www.brentozar.com/pastetheplan/?id=BkpZU1MZE에 있으며, 쿼리 지속 시간은 4:37 분이며 26612 행이 반환됩니다.

이전에 기존 테이블에서 이러한 유형의 상수 스캔이 생성되는 것을 보지 못했습니다. 사용자가 입력 한 단일 행에 대해 일정한 스캔이 일반적으로 사용될 때 모든 단일 행에 대해 연속 스캔이 실행되는 이유에 익숙하지 않습니다. 예를 들어, SELECT GETDATE (). 왜 여기에 사용됩니까? 이 쿼리 계획을 읽는 데 도움이 될만한 조언을 부탁드립니다.

그 OR을 UNION으로 분리하면 동일한 26612 행이 반환 된 12 초 안에 실행되는 표준 계획이 생성됩니다.

SELECT Users.Id
FROM dbo.Users
    INNER JOIN dbo.Posts
       ON Users.Id = Posts.OwnerUserId
WHERE Users.Reputation = 5
UNION 
SELECT Users.Id
FROM dbo.Users
    INNER JOIN dbo.Posts
       ON  Users.Id = Posts.LastEditorUserId
WHERE Users.Reputation = 5

이 계획을 다음과 같이 해석합니다.

  • 게시물에서 41782500 행을 모두 가져옵니다 (실제 행 수는 게시물의 CI 스캔과 일치합니다)
  • 게시물의 각 41782500 행에 대해 :
    • 스칼라 생성 :
    • Expr1005 : OwnerUserId
    • Expr1006 : OwnerUserId
    • Expr1004 : 정적 값 62
    • Expr1008 : LastEditorUserId
    • Expr1009 : LastEditorUserId
    • Expr1007 : 정적 값 62
  • 연결에서 :
    • Exp1010 : Expr1005 (OwnerUserId)가 널이 아닌 경우 Expr1008 (LastEditorUserID)을 사용하십시오.
    • Expr1011 : Expr1006 (OwnerUserId)이 널이 아닌 경우 Expr1009 (LastEditorUserId)를 사용하십시오.
    • Expr1012 : Expr1004 (62)가 null이면 Expr1007 (62)을 사용하십시오.
  • Compute 스칼라에서 : 앰퍼샌드가 무엇을하는지 모르겠습니다.
    • Expr1013 : 4 [and?] 62 (Expr1012) = 4이고 OwnerUserId는 NULL입니다 (NULL = Expr1010)
    • expr1014 : 4 [및?] 62 (Expr1012)
    • Expr1015 : 16 및 62 (Expr1012)
  • 정렬 기준 :
    • Expr1013 설명
    • Expr1014 Asc
    • Expr1010 Asc
    • Expr1015 설명
  • 병합 간격에서 Expr1013 및 Expr1015를 제거했습니다 (이것은 입력이지만 출력은 아닙니다)
  • 인덱스 탐색 아래의 중첩 루프 조인에서 Expr1010과 Expr1011을 탐색 술어로 사용하고 있지만 IX_NC_REPUTATION에서 Expr1010 및 Expr1011이 포함 된 하위 트리로 중첩 루프 조인을 수행하지 않은 경우 어떻게 액세스 할 수 있는지 이해할 수 없습니다. .
  • 중첩 루프 조인은 이전 하위 트리에서 일치하는 Users.ID 만 반환합니다. 술어 푸시 다운으로 인해 IX_NC_REPUTATION의 인덱스 검색에서 리턴 된 모든 행이 리턴됩니다.
  • 마지막 중첩 루프 조인 : 각 게시물 레코드에 대해 아래 데이터 집합에서 일치하는 Users.Id를 출력합니다.

EXISTS 하위 쿼리 또는 하위 쿼리를 사용해 보셨습니까? SELECT Users.Id FROM dbo.Users WHERE Users.Reputation = 5 AND ( EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id = Posts.OwnerUserId) OR EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id = Posts.LastEditorUserId) ) ;
ypercubeᵀᴹ

하나의 하위 쿼리 :SELECT Users.Id FROM dbo.Users WHERE Users.Reputation = 5 AND EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id IN (Posts.OwnerUserId, Posts.LastEditorUserId) ) ;
ypercubeᵀᴹ 11:27에

답변:


10

이 계획은 제가 여기서 자세히 다루는 것과 비슷합니다 .

Posts테이블 스캔됩니다.

각 행마다 OwnerUserIdand를 추출합니다 LastEditorUserId. 이것은 UNPIVOT작동 방식과 비슷 합니다. 아래에서 각 입력 행에 대해 두 개의 출력 행을 작성하기위한 단일 상수 스캔 연산자를 볼 수 있습니다.

SELECT *
FROM dbo.Posts
UNPIVOT (X FOR U IN (OwnerUserId,LastEditorUserId)) Unpvt

이 경우 계획은 의미가 있기 때문에 약간 더 복잡 or합니다. 두 열 값이 동일하면 하나의 행만 조인에서 방출되어야합니다 Users(2 개가 아님).

그런 다음 병합 간격을 통해 값이 동일하면 범위가 축소되고 하나의 탐색 만 Users실행됩니다. 그렇지 않으면 두 번의 탐색이 실행됩니다.

62은 탐색이 평등 탐색이어야 함을 의미하는 플래그입니다.

에 관해서

IX_NC_REPUTATION에서 Expr1010 및 Expr1011을 포함하는 하위 트리로 중첩 루프 조인을 수행하지 않았을 때 어떻게 액세스 할 수 있는지 이해할 수 없습니다.

이것들은 노란색으로 강조 표시된 연결 연산자로 정의됩니다. 이것은 노란색으로 강조 표시된 중첩 루프의 바깥쪽에 있습니다. 따라서 이것은 중첩 된 루프 내부에서 노란색으로 강조 표시된 탐색 전에 실행됩니다.

여기에 이미지 설명을 입력하십시오

이것이 도움이되는 경우 비슷한 계획을 제공하는 재 작성 (병합 간격이 합집합으로 대체되었지만)은 다음과 같습니다.

SELECT DISTINCT D2.UserId
FROM   dbo.Posts p
       CROSS APPLY (SELECT Users.Id AS UserId
                    FROM   (SELECT p.OwnerUserId
                            UNION /*collapse duplicate to single row*/
                            SELECT p.LastEditorUserId) D1(UserId)
                           JOIN Users
                             ON Users.Id = D1.UserId) D2
OPTION (FORCE ORDER) 

여기에 이미지 설명을 입력하십시오

Posts테이블에서 사용 가능한 인덱스에 따라이 쿼리의 변형이 제안 된 UNION ALL솔루션 보다 더 효율적일 수 있습니다 . (내가 가지고있는 데이터베이스의 사본에는 이것에 대한 유용한 색인이 없으며 제안 된 솔루션은 두 번의 전체 스캔을 수행 Posts합니다. 아래는 한 번의 스캔으로 수행합니다)

WITH Unpivoted AS
(
SELECT UserId
FROM dbo.Posts
UNPIVOT (UserId FOR U IN (OwnerUserId,LastEditorUserId)) Unpivoted
)
SELECT DISTINCT Users.Id
FROM dbo.Users INNER HASH JOIN Unpivoted
       ON  Users.Id = Unpivoted.UserId
WHERE Users.Reputation = 5

여기에 이미지 설명을 입력하십시오

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