web-dev-qa-db-ja.com

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

このことに出くわしたとき、私は何か他のものを研究していました。いくつかのデータを含むテストテーブルを生成し、さまざまなクエリを実行して、クエリを記述するさまざまな方法が実行プランにどのように影響するかを調べました。以下は、ランダムなテストデータを生成するために使用したスクリプトです。

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
;

驚いたことに、このクエリに対して生成された実行プランは this でした。 (申し訳ありませんが、外部リンクは大きすぎてここに収まりません)。

誰かがこれらすべての " 定数スキャン "と " コンピュートスカラー "の問題を説明してくれませんか?何が起こっていますか?

Plan

  |--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)
20
Andrew Savinykh

定数スキャンはそれぞれ、列のない単一のメモリ内行を生成します。一番上の計算スカラーは、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のビットがオンで、Expr1010NULLの場合、ブール列trueを返すようです。

クエリで他の比較演算子を試すと、これらの結果が得られます

+----------+----------+----------+-------------+----+----+---+---+---+---+
| 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列の結果セットは、Expr1013 DESC, Expr1014 ASC, Expr1010 ASC, Expr1015 DESCでソートされたSORT演算子から出力されます。 True1で表され、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つのシークが実行されているように見えます。 1つは> NULL AND < NULLで、もう1つは> 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が 以前にリンクされたブログ投稿 へのコメントで提供しています。

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

29
Martin Smith

定数スキャンは、SQL Serverがバケットを作成する方法であり、実行プランの後半にバケットを配置します。私はもっ​​と投稿しました ここでのそれの完全な説明 。常時スキャンの目的を理解するには、計画をさらに詳しく調べる必要があります。この場合、定数スキャンによって作成されたスペースを埋めるために使用されているのは、Compute Scalarオペレーターです。

Compute Scalarオペレーターには、NULLと値1045876が読み込まれているため、データをフィルター処理する目的でループ結合で使用されることは明らかです。

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

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

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

13
Grant Fritchey