이 실행 계획을 설명 할 수 있습니까?


20

이 문제를 발견했을 때 다른 것을 연구하고있었습니다. 몇 가지 데이터가 포함 된 테스트 테이블을 생성하고 다른 쿼리를 실행하여 쿼리 작성 방법이 실행 계획에 어떤 영향을 미치는지 알아 냈습니다. 무작위 테스트 데이터를 생성하는 데 사용한 스크립트는 다음과 같습니다.

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID('t') AND type in (N'U'))
DROP TABLE t
GO

CREATE TABLE t 
(
 c1 int IDENTITY(1,1) NOT NULL 
,c2 int NULL
) 
GO

insert into t
select top 1000000 a from
(select t1.number*2048 + t2.number a, newid() b
from [master]..spt_values t1 
cross join  [master]..spt_values t2
where t1.[type] = 'P' and t2.[type] = 'P') a
order by b
GO

update t set c2 = null
where c2 < 2048 * 2048 / 10
GO


CREATE CLUSTERED INDEX pk ON [t] (c1)
GO

CREATE NONCLUSTERED INDEX i ON t (c2)
GO

이제이 데이터가 주어지면 다음 쿼리를 호출했습니다.

select * 
from t 
where 
      c2 < 1048576 
   or c2 is null
;

놀랍게도이 쿼리에 대해 생성 된 실행 계획은 이것 입니다. (외부 링크가 유감이므로 여기에 맞지 않습니다.)

누군가이 " Constant Scans "와 " Compute Scalars "가 어떻게 되는지 설명해 줄 수 있습니까 ? 무슨 일이야?

계획

  |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1010], [Expr1011], [Expr1012]))
       |--Merge Interval
       |    |--Sort(TOP 2, ORDER BY:([Expr1013] DESC, [Expr1014] ASC, [Expr1010] ASC, [Expr1015] DESC))
       |         |--Compute Scalar(DEFINE:([Expr1013]=((4)&[Expr1012]) = (4) AND NULL = [Expr1010], [Expr1014]=(4)&[Expr1012], [Expr1015]=(16)&[Expr1012]))
       |              |--Concatenation
       |                   |--Compute Scalar(DEFINE:([Expr1005]=NULL, [Expr1006]=NULL, [Expr1004]=(60)))
       |                   |    |--Constant Scan
       |                   |--Compute Scalar(DEFINE:([Expr1008]=NULL, [Expr1009]=(1048576), [Expr1007]=(10)))
       |                        |--Constant Scan
       |--Index Seek(OBJECT:([t].[i]), SEEK:([t].[c2] > [Expr1010] AND [t].[c2] < [Expr1011]) ORDERED FORWARD)

답변:


29

상수 스캔은 각각 열이없는 단일 메모리 내 행을 생성합니다. 최상위 계산 스칼라는 3 개의 열이있는 단일 행을 출력합니다.

Expr1005    Expr1006    Expr1004
----------- ----------- -----------
NULL        NULL        60

하단 계산 스칼라는 3 개의 열이있는 단일 행을 출력합니다.

Expr1008    Expr1009    Expr1007
----------- ----------- -----------
NULL        1048576        10

연결 연산자는이 2 개의 행을 결합하여 3 개의 열을 출력하지만 이름이 바뀌 었습니다.

Expr1010    Expr1011    Expr1012
----------- ----------- -----------
NULL        NULL        60
NULL        1048576     10

Expr1012열은 Storage Engine의 특정 검색 속성을 정의하기 위해 내부적으로 사용되는 플래그 집합입니다 .

다음 계산 스칼라는 2 행을 출력합니다.

Expr1010    Expr1011    Expr1012    Expr1013    Expr1014    Expr1015
----------- ----------- ----------- ----------- ----------- -----------
NULL        NULL        60          True        4           16            
NULL        1048576     10          False       0           0      

마지막 세 열은 다음과 같이 정의되며 병합 간격 연산자에 제시하기 전에 정렬 목적으로 만 사용됩니다.

[Expr1013] = Scalar Operator(((4)&[Expr1012]) = (4) AND NULL = [Expr1010]), 
[Expr1014] = Scalar Operator((4)&[Expr1012]), 
[Expr1015] = Scalar Operator((16)&[Expr1012])

Expr1014그리고 Expr1015단지 테스트 특정 비트가 플래그에 있는지. Expr1013에 대한 비트가 모두 4켜져 있고 Expr1010가 있으면 부울 열 true를 반환하는 것으로 나타납니다 NULL.

쿼리에서 다른 비교 연산자를 시도하여 이러한 결과를 얻습니다.

+----------+----------+----------+-------------+----+----+---+---+---+---+
| Operator | Expr1010 | Expr1011 | Flags (Dec) |       Flags (Bin)       |
|          |          |          |             | 32 | 16 | 8 | 4 | 2 | 1 |
+----------+----------+----------+-------------+----+----+---+---+---+---+
| >        | 1048576  | NULL     |           6 |  0 |  0 | 0 | 1 | 1 | 0 |
| >=       | 1048576  | NULL     |          22 |  0 |  1 | 0 | 1 | 1 | 0 |
| <=       | NULL     | 1048576  |          42 |  1 |  0 | 1 | 0 | 1 | 0 |
| <        | NULL     | 1048576  |          10 |  0 |  0 | 1 | 0 | 1 | 0 |
| =        | 1048576  | 1048576  |          62 |  1 |  1 | 1 | 1 | 1 | 0 |
| IS NULL  | NULL     | NULL     |          60 |  1 |  1 | 1 | 1 | 0 | 0 |
+----------+----------+----------+-------------+----+----+---+---+---+---+

여기서 비트 4는 "범위가 시작되지 않은"범위를 의미하며 비트 16은 범위의 시작이 포함됨을 의미합니다.

이 6 개의 열 결과 집합은로 SORT정렬 된 연산자 에서 생성 됩니다 Expr1013 DESC, Expr1014 ASC, Expr1010 ASC, Expr1015 DESC. 가정 True에 의해 표현 1False에 의해 0이전에 표현되는 결과 집합은 순서 이미 사용 중입니다.

이전 가정을 기반으로이 정렬의 순 효과는 범위를 다음 순서로 병합 간격에 표시하는 것입니다.

 ORDER BY 
          HasStartOfRangeAndItIsNullFirst,
          HasUnboundedStartOfRangeFirst,
          StartOfRange,
          StartOfRangeIsInclusiveFirst

병합 간격 연산자는 2 개의 행을 출력합니다.

Expr1010    Expr1011    Expr1012
----------- ----------- -----------
NULL        NULL        60
NULL        1048576     10

방출 된 각 행에 대해 범위 탐색이 수행됩니다.

Seek Keys[1]: Start:[dbo].[t].c2 > Scalar Operator([Expr1010]), 
               End: [dbo].[t].c2 < Scalar Operator([Expr1011])

따라서 두 번의 탐색이 수행되는 것처럼 보입니다. 하나는 분명히 > NULL AND < NULL하나 > NULL AND < 1048576. 그러나 전달받을 플래그는이를 수정할 표시 IS NULL하고 < 1048576각각. 잘하면 @sqlkiwi 가 이것을 명확히하고 부정확성을 수정할 수 있기를 바랍니다 !

검색어를 약간 변경하면

select *
from t 
where 
      c2 > 1048576 
   or c2 = 0
;

그런 다음 다중 탐색 술어를 사용하는 인덱스 탐색으로 계획이 훨씬 단순 해 보입니다.

계획은 보여줍니다 Seek Keys

Start: c2 >= 0, End: c2 <= 0, 
Start: c2 > 1048576

OP에서이 단순한 계획을 사용할 수없는 이유에 대한 설명은 SQLKiwi가 이전에 링크 된 블로그 게시물 에 대한 주석에서 제공합니다 .

여러 조건을 비교 조건 (예. 다른 종류의 혼합 할 수 없습니다와 인덱스는 노력 IsEq영업 이익의 경우)를. 이 제품의 단지 전류 제한 (그리고 아마도 마지막 쿼리의 평등 테스트가 이유입니다 c2 = 0사용하여 구현 >=하고 <=오히려 그냥 간단 평등보다 쿼리 얻을 추구 c2 = 0 OR c2 = 1048576.


폴의 기사에서 [Expr1012]의 플래그 차이를 설명하는 내용은 찾을 수 없습니다. 여기서 60/10이 무엇을 의미하는지 추론 할 수 있습니까?
Mark Storey-Smith

@ MarkStorey-Smith- 62평등 비교를위한 것이라고 합니다. 나는 아마도 명시 적 플래그 가 아닌 한 아마도 계획에 나와있는 60대신에 아마도 (?) 또는 비트 가 관련이 없으며 다른 것으로 표시하고 그것을 바꿀 때 와 여전히 평등 하다는 것을 의미한다고 생각해야 합니다.> AND < >= AND <=IS NULL260set ansi_nulls offc2 = null60
Martin Smith

2
@MartinSmith 60은 실제로 NULL과 비교하기위한 것입니다. 범위 경계 표현식은 NULL을 사용하여 양쪽 끝에 '언 바운드'를 나타냅니다. 탐색은 항상 배타적입니다. 즉, 탐색 :> Expr & End : <Expr >> 및 <=을 사용하는 것이 아닙니다. 블로그 댓글에 감사드립니다. 나는 아침에 답장을 보내거나 더 긴 댓글을 게시 할 것입니다 (지금은 너무 늦었습니다).
Paul White에 따르면 GoFundMonica는

@SQLKiwi-감사합니다. 말이 되네요 잘만되면 나는 그 전에 누락 된 비트 중 일부를 알아낼 것입니다.
Martin Smith

정말 고마워요, 나는 여전히 이것을 흡수하고 있지만, 잘 설명하는 것처럼 보입니다. 남은 주요 질문은 블로그에서 @SQLKiwi에게 묻는 질문입니다. 후속 질문이 없는지 확인하기 위해 귀하의 답변에 대해 며칠 더 묵상하고 귀하의 답변을 수락하겠습니다. 다시 한 번 감사드립니다. 큰 도움이되었습니다.
앤드류 Savinykh

13

상수 스캔은 SQL Server가 나중에 실행 계획에 무언가를 배치 할 버킷을 생성하는 방법입니다. 이에 대한 자세한 설명을 여기에 게시 했습니다 . 지속적인 스캔이 무엇인지 이해하려면 계획을 자세히 살펴 봐야합니다. 이 경우 상수 스캔으로 생성 된 공간을 채우는 데 사용되는 것은 Compute Scalar 연산자입니다.

Compute Scalar 연산자는 NULL 및 값 1045876으로로드되므로 데이터를 필터링하기 위해 루프 조인과 함께 명확하게 사용됩니다.

정말 멋진 부분은이 계획이 간단하다는 것입니다. 최소 최적화 프로세스를 거쳤 음을 의미합니다. 모든 작업은 병합 간격으로 진행됩니다. 인덱스 검색을위한 최소 비교 연산자 세트를 작성하는 데 사용됩니다 ( 자세한 내용은 here ).

전체 아이디어는 중복 값을 제거하여 최소한의 패스로 데이터를 가져올 수 있도록하는 것입니다. 여전히 루프 작업을 사용하고 있지만 루프는 정확히 한 번만 실행됩니다. 이는 사실상 스캔입니다.

부록 : 마지막 문장이 꺼져 있습니다. 두 가지 탐색이있었습니다. 나는 계획을 잘못 읽었다. 나머지 개념은 동일하며 최소한의 패스라는 목표는 동일합니다.

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