この実行計画を説明できますか?


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列には、フラグのセットである特定のストレージエンジンのプロパティを求める定義するために内部的に使用します

出力に沿った次の計算スカラー2行

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

最後の3つの列は次のように定義され、マージ間隔演算子に表示する前の並べ替え目的でのみ使用されます

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

Expr1014そしてExpr1015、特定のビットがフラグにオンであるかどうかだけテスト。 Expr1013以下のための両方のビットがあれば、真のブール列を返すように見える4上にあり、Expr1010です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は、以前に表された結果セットによって表され1、すでにその順序になっています。False0

私の以前の仮定に基づいて、この種の最終的な効果は、次の順序で範囲をマージ間隔に提示することです

 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])

そのため、2つのシークが実行されているように見えます。どうやら> NULL AND < NULL1つ > 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によって与えられています。

複数の述語が比較述語(すなわち、異なるタイプの混在させることはできませんし、インデックスシークIsEqOPでのケースでは)。これは、製品の現在の制限にすぎません(おそらく、クエリc2 = 0で取得する単純な等価性シークではなく、最後のクエリの等価性テストを使用>=して実装する理由<=ですc2 = 0 OR c2 = 1048576


[Expr1012]のフラグの違いを説明するPaulの記事には何も見当たりません。ここで60/10が意味するものを推測できますか?
マークストーリースミス

@ MarkStorey-Smith-彼は62平等比較のためだと言います。私は推測する60ことを意味するの代わりに、必要があり> AND < 、実際の取得で計画あなたのように>= AND <=それが明示的でない限りIS NULL(?)かもしれないフラグまたは多分ビットは2関係のない何か他のものを示しており、60まだ私が行うときのように平等であるset ansi_nulls offとに変更しc2 = null、まだ滞在で、それ60
マーティンスミス

2
@MartinSmith 60は、実際にはNULLとの比較用です。範囲境界式はNULLを使用して、両端で「無制限」を表します。シークは常に排他的です。つまり、シークStart:> Expr&End:<Exprではなく、> =および<=を使用してシークします。ブログのコメントのおかげで、午前中に回答またはより長いコメントを投稿します(今すぐ正しすぎるには遅すぎます)。
ポールホワイトはGoFundMonicaを言う

@SQLKiwi-ありがとう。それは理にかなっている。うまく行けば、それまでに不足しているビットの一部を把握できたことでしょう。
マーティンスミス

ありがとうございます、私はまだこれを吸収していますが、物事をうまく説明しているようです、残されている主な質問は彼のブログで@SQLKiwiに尋ねているものです。フォローアップの質問がないことを確認するために、あなたの答えをさらに数日瞑想し、あなたの答えを受け入れます。再びありがとう、それは大きな助けでした。
アンドリューサヴィニク

13

定数スキャンは、SQL Serverがバケットを作成して、実行プランの後半に何かを配置する方法です。ここでより詳細な説明を投稿しまし。常時スキャンの目的を理解するには、計画をさらに検討する必要があります。この場合、コンスタントスキャンによって作成されたスペースにデータを入力するために使用されているのは計算スカラー演算子です。

Compute Scalar演算子にはNULLと値1045876がロードされているため、データをフィルター処理するために、ループ結合で明らかに使用されます。

本当にクールな部分は、この計画が簡単なことです。最小限の最適化プロセスを経たことを意味します。すべての操作は、マージ間隔に至っています。これは、インデックスシーク用の比較演算子の最小セットを作成するために使用されます(詳細はこちら)。

全体的な考え方は、重複する値を取り除き、最小限のパスでデータを引き出すことです。まだループ操作を使用していますが、ループは1回だけ実行されることに注意してください。つまり、実質的にはスキャンです。

補遺:その最後の文はオフです。2つのシークがありました。計画を読み間違えました。残りの概念は同じであり、目標である最小パスは同じです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.