スパース列、CPU時間、フィルターされたインデックス


10

スパース

スパース列でいくつかのテストを行うとき、あなたがそうであるように、直接的な原因を知りたいパフォーマンスの低下がありました。

DDL

2つの同一のテーブルを作成しました。1つはスパース列が4つあり、もう1つはスパース列がないものです。

--Non Sparse columns table & NC index
CREATE TABLE dbo.nonsparse( ID INT IDENTITY(1,1) PRIMARY KEY NOT NULL,
                      charval char(20) NULL,
                      varcharval varchar(20) NULL,
                      intval int NULL,
                      bigintval bigint NULL
                      );
CREATE INDEX IX_Nonsparse_intval_varcharval
ON dbo.nonsparse(intval,varcharval)
INCLUDE(bigintval,charval);

-- sparse columns table & NC index

CREATE TABLE dbo.sparse( ID INT IDENTITY(1,1) PRIMARY KEY NOT NULL,
                      charval char(20) SPARSE NULL ,
                      varcharval varchar(20) SPARSE NULL,
                      intval int SPARSE NULL,
                      bigintval bigint SPARSE NULL
                      );

CREATE INDEX IX_sparse_intval_varcharval
ON dbo.sparse(intval,varcharval)
INCLUDE(bigintval,charval);

DML

次に、約2540のNON-NULL値を両方に挿入しました。

INSERT INTO dbo.nonsparse WITH(TABLOCK) (charval, varcharval,intval,bigintval)
SELECT 'Val1','Val2',20,19
FROM MASTER..spt_values;

INSERT INTO dbo.sparse WITH(TABLOCK) (charval, varcharval,intval,bigintval)
SELECT 'Val1','Val2',20,19
FROM MASTER..spt_values;

その後、1MのNULL値を両方のテーブルに挿入しました

INSERT INTO dbo.nonsparse WITH(TABLOCK)  (charval, varcharval,intval,bigintval)
SELECT TOP(1000000) NULL,NULL,NULL,NULL 
FROM MASTER..spt_values spt1
CROSS APPLY MASTER..spt_values spt2;

INSERT INTO dbo.sparse WITH(TABLOCK) (charval, varcharval,intval,bigintval)
SELECT TOP(1000000) NULL,NULL,NULL,NULL 
FROM MASTER..spt_values spt1
CROSS APPLY MASTER..spt_values spt2;

クエリ

非スパーステーブルの実行

新しく作成された非スパーステーブルでこのクエリを2回実行すると、次のようになります。

SET STATISTICS IO, TIME ON;
SELECT  * FROM dbo.nonsparse
WHERE   1= (SELECT 1) -- force non trivial plan
OPTION(RECOMPILE,MAXDOP 1);

論理読み取りは5257ページを示します

(1002540 rows affected)
Table 'nonsparse'. Scan count 1, logical reads 5257, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

そしてCPU時間は343ミリ秒です

 SQL Server Execution Times:
   CPU time = 343 ms,  elapsed time = 3850 ms.

スパーステーブルの実行

スパーステーブルで同じクエリを2回実行します。

SELECT  * FROM dbo.sparse
WHERE   1= (SELECT 1) -- force non trivial plan
OPTION(RECOMPILE,MAXDOP 1);

読み取りはより低く、1763

(1002540 rows affected)
Table 'sparse'. Scan count 1, logical reads 1763, physical reads 3, read-ahead reads 1759, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

ただし、CPU時間は547ミリ秒と長くなります。

 SQL Server Execution Times:
   CPU time = 547 ms,  elapsed time = 2406 ms.

スパーステーブル実行プラン

非スパーステーブル実行プラン


ご質問

元の質問

以来NULLの値がスパース列に直接格納されていない、CPU時間の増加は、返すことに起因する可能性がNULLの結果セットとして値を?それとも、ドキュメントに記載されいるような動作ですか?

スパース列は、null以外の値を取得するためのオーバーヘッドが増える代わりに、null値のスペース要件を減らします。

または、オーバーヘッドは使用される読み取りとストレージにのみ関連していますか?

実行後の破棄結果オプションを使用してssmsを実行した場合でも、非スパース選択のCPU時間は非スパース(219ミリ秒)に比べて長くなりました(407ミリ秒)。

編集

2540しかない場合でも、null以外の値のオーバーヘッドであった可能性がありますが、それでも確信はありません。

これはほぼ同じパフォーマンスのようですが、まばらな要素が失われました。

CREATE INDEX IX_Filtered
ON dbo.sparse(charval,varcharval,intval,bigintval)
WHERE charval IS NULL  
      AND varcharval IS NULL
      AND intval  IS NULL
      AND bigintval  IS NULL;

CREATE INDEX IX_Filtered
ON dbo.nonsparse(charval,varcharval,intval,bigintval)
WHERE charval IS NULL  
      AND varcharval IS NULL
      AND intval  IS NULL
      AND bigintval  IS NULL;


    SET STATISTICS IO, TIME ON;

SELECT  charval,varcharval,intval,bigintval FROM dbo.sparse WITH(INDEX(IX_Filtered))
WHERE charval IS NULL AND  varcharval IS NULL
                     AND intval  IS NULL
                     AND bigintval  IS NULL
                     OPTION(RECOMPILE,MAXDOP 1);


SELECT  charval,varcharval,intval,bigintval 
FROM dbo.nonsparse WITH(INDEX(IX_Filtered))
WHERE charval IS NULL AND 
                      varcharval IS NULL
                     AND intval  IS NULL
                     AND bigintval  IS NULL
                     OPTION(RECOMPILE,MAXDOP 1);

ほぼ同じ実行時間を持っているようです:

 SQL Server Execution Times:
   CPU time = 297 ms,  elapsed time = 292 ms.

 SQL Server Execution Times:
   CPU time = 281 ms,  elapsed time = 319 ms.

しかし、なぜ論理的な読み取りが同じ量になるのでしょうか。スパース列のフィルター処理されたインデックスには、含まれているIDフィールドと他のいくつかの非データページ以外は何も格納しないでください。

Table 'sparse'. Scan count 1, logical reads 5785,
Table 'nonsparse'. Scan count 1, logical reads 5785

そして、両方のインデックスのサイズ:

RowCounts   Used_MB Unused_MB   Total_MB
1000000     45.20   0.06        45.26

なぜ同じサイズなのですか?まばらさが失われましたか?

フィルターされたインデックスを使用する場合の両方のクエリプラン


追加情報

select @@version

Microsoft SQL Server 2017(RTM-CU16)(KB4508218)-14.0.3223.3(X64)2019年7月12日17:43:08 Copyright(C)2017 Microsoft Corporation Developer Edition(64-bit)on Windows Server 2012 R2 Datacenter 6.3(Build 9600:)(ハイパーバイザー)

クエリを実行し、IDフィールドのみを選択している間は、CPU時間は同等であり、スパーステーブルの論理読み取りが少なくなります。

テーブルのサイズ

SchemaName  TableName   RowCounts   Used_MB Unused_MB   Total_MB
dbo         nonsparse   1002540     89.54   0.10        89.64
dbo         sparse      1002540     27.95   0.20        28.14

クラスター化インデックスまたは非クラスター化インデックスのいずれかを強制すると、CPU時間の差が残ります。


1
編集後のクエリの計画を教えてもらえますか?
George.Palacios

1
@ George.Palaciosが追加しました:)
Randi Vertongen

回答:


6

それとも、ドキュメントに記載されているような動作ですか?

そうそう。ドキュメントで言及されている「オーバーヘッド」は、CPUオーバーヘッドのようです。

2つのクエリのプロファイルを作成すると、スパースクエリは367ミリ秒のCPUをサンプリングしましたが、非スパースクエリは284ミリ秒のCPUをサンプリングしました。これは83ミリ秒の差です。

クエリを実行したスレッドの合計CPUを示すPerfviewのスクリーンショット

そのほとんどはどこですか?

両方のプロファイルは、に到達するまで非常によく似ていますsqlmin!IndexDataSetSession::GetNextRowValuesInternal。その時点で、スパースコードは実行するパスをたどりsqlmin!IndexDataSetSession::GetDataLong、スパース列機能(HasSparseVectorStoreColumnValue)に関連しているように見えるいくつかの関数を呼び出し、合計で(42 + 11 =)53 msになります。

スパース列のCPUの違いのスクリーンショット

なぜ同じサイズなのですか?まばらさが失われましたか?

ええ、スパース列をインデックスキーとして使用すると、スパースストレージ最適化は非クラスター化インデックスに引き継がれないようです。そのため、非クラスター化インデックスキー列は、スパース性に関係なくフルサイズを占有しますが、インクルードされた列は、スパースでNULLの場合、スペースをまったく使用しません。

見てDBCC PAGENULL値スパース列のクラスタ化インデックスページからの出力、Iは、レコード長は11(標準単位のレコードオーバーヘッドのID + 7 4)であることがわかります。

Record Type = PRIMARY_RECORD        Record Attributes =  NULL_BITMAP    Record Size = 11

フィルター処理されたインデックスの場合、レコードは常に40です。これは、すべてのキー列のサイズの合計です(4バイトID + 20バイトcharval + 4バイトvarcharval + 4バイトintval + 8バイトbig intval = 40バイト)。

何らかの理由で、DBCC PAGEインデックスレコードの「レコードサイズ」に7バイトのオーバーヘッドが含まれていません。

Record Type = INDEX_RECORD          Record Attributes =  NULL_BITMAP    Record Size = 40

フィルタリングされていないインデックスサイズは小さくなります(4バイトのID + 4バイトのintval + 4バイトのvarcharval = 12バイト)。これは、2つのスパース列が含まれているため、スパース性の最適化が行われるためです。

Record Type = INDEX_RECORD          Record Attributes =  NULL_BITMAP    Record Size = 12

この動作の違いは、ドキュメントページにリストされている制限の1つと一致していると思います。

スパース列をクラスター化インデックスまたは一意の主キーインデックスの一部にすることはできません

非クラスター化インデックスのキーにすることはできますが、まばらに格納されません。


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