クラスター化インデックスのように本質的に順序付けられたデータ


8

750万レコードの次の表があります。

CREATE TABLE [dbo].[TestTable](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [TestCol] [nvarchar](50) NOT NULL,
    [TestCol2] [nvarchar](50) NOT NULL,
    [TestCol3] [nvarchar](50) NOT NULL,
    [Anonymised] [tinyint] NOT NULL,
    [Date] [datetime] NOT NULL,
CONSTRAINT [PK_TestTable] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

日付フィールドに非クラスター化インデックスがある場合、

CREATE NONCLUSTERED INDEX IX_TestTable_Date ON [dbo].[TestTable] ([Date])

-そして私は次のクエリを実行します:

UPDATE TestTable 
SET TestCol='*GDPR*', TestCol2='*GDPR*', TestCol3='*GDPR*', Anonymised=1
WHERE [Date] <= '25 August 2016'

-インデックスアクセス操作によって返されるデータは、PK / CXのキーの順序と一致するようにソートされ、パフォーマンスが低下します。

クエリプラン

日付フィールドからインデックスを削除すると、並べ替えが実行されなくなるため、クエリのパフォーマンスが実際に約30%向上することに驚きました。

クエリプラン

私の理論は、経験豊富な人には明らかかもしれませんが、日付列は暗黙的に主キー/クラスター化インデックスとまったく同じ順序で並べられていることがわかりました。

だから私の質問は:私のクエリのパフォーマンスを向上させるためにこの事実を利用することは可能ですか?


1
私は計画を見ていませんが、ソート操作のためではなく、削除したインデックスを更新する必要がなくなったため、パフォーマンス(まあ、期間、これらの無駄な推定コスト%の数値はありません)は改善されたと思います。
アーロンバートランド

@AaronBertrand私はこれらを誤って読んでいる可能性があるので、私が間違っている場合は訂正してください。ただし、両方のクエリプランにインデックス更新操作があるようです。他のことを言っているのですか?
AproposArmadillo

1
繰り返しますが、私は計画を見ていないと言いました。「日付フィールドからインデックスを削除すると、クエリのパフォーマンスが向上します」と言いました...インデックスを削除した場合、プランには表示されないため、間違ったプランを収集したか、実際に削除しなかった可能性がありますあなたがしたと思ったインデックス。繰り返しになりますが、計画の一部の推定%は指標ですが、実際には実際のパフォーマンス測定を反映するものではありません。これは、クエリが実行される前に計算される推定値です。
アーロンバートランド

@Aaron Bertrand、とにかく[Date]が更新されたフィールドに含まれていなかったため、インデックスを更新する必要はありませんでした。
Denis Rubashkin

1
@Shaffanhoon 順序どおりにインデックスを再作成してみまし[Date]DESCか?述語はなので、気になります<=。また、Date(デフォルトでは、ACS順序で)インデックスが他のクエリに役立つ場合は、テーブルヒントをUPDATEに追加してPKを使用するように強制することができますか?または、これを2つの部分に分けます。一時テーブルを作成し、に[Id]基づいてデータを入力し[Date] <= '25 August 2016'WHEREから、UPDATEからを削除して追加しFROM dbo.TestTable tt INNER JOIN #tmp ids ON ids.[Id] = tt.[Id]ます。結局のところUPDATEであり、実際の行、インデックス、またはいいえを見つける必要があります。
ソロモンRutzky

回答:


7

私はあなたの問題をほとんど再現するテストデータをモックアップしました:

INSERT INTO [dbo].[TestTable] WITH (TABLOCK)
SELECT TOP (7000000) N'*NOT GDPR*', N'*NOT GDPR*', N'*NOT GDPR*', 0, DATEADD(DAY, q.RN  / 16965, '20160801')
FROM
(
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) q
ORDER BY q.RN
OPTION (MAXDOP 1);


DROP INDEX IF EXISTS [dbo].[TestTable].IX_TestTable_Date;
CREATE NONCLUSTERED INDEX IX_TestTable_Date ON [dbo].[TestTable] ([Date]);

非クラスター化インデックスを使用するクエリの統計:

テーブル 'TestTable'。スキャンカウント1、論理読み取り1299838、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。

SQL Server実行時間:CPU時間= 984ミリ秒、経過時間= 988ミリ秒。

クラスタ化インデックスを使用するクエリの統計:

テーブル 'TestTable'。スキャンカウント1、論理読み取り72609、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。

SQL Server実行時間:CPU時間= 781ミリ秒、経過時間= 772ミリ秒。

あなたの質問に行く:

クエリのパフォーマンスを向上させるためにこの事実を利用することは可能ですか?

はい。id更新する必要のある最大値を効率的に見つけるために既に持っている非クラスター化インデックスを使用できます。これを変数に保存してフィルター処理すると、クラスター化インデックススキャン(並べ替えなし)を実行する更新のクエリプランが得られ、早期に停止するためIOが少なくなります。ここに1つの実装があります:

DECLARE @Id INT;

SELECT TOP (1) @Id = Id
FROM dbo.TestTable 
WHERE [Date] <= '25 August 2016'
ORDER BY [Date] DESC, Id DESC;

UPDATE TestTable 
SET TestCol='*GDPR*', TestCol2='*GDPR*', TestCol3='*GDPR*', Anonymised=1
WHERE [Id] < @Id AND [Date] <= '25 August 2016'
AND [Anonymised] <> 1 -- optional
OPTION (MAXDOP 1);

新しいクエリの統計を実行します。

テーブル 'TestTable'。スキャンカウント1、論理読み取り3、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。

テーブル 'TestTable'。スキャンカウント1、論理読み取り4776、物理読み取り0、先読み読み取り0、LOB論理読み取り0、LOB物理読み取り0、LOB先読み読み取り0。

SQL Server実行時間:CPU時間= 515ミリ秒、経過時間= 510ミリ秒。

クエリプランと同様に:

わかりましたクエリプラン

以上のことから、クエリをより高速にしたいという要望から、クエリを複数回実行することを計画していることがわかります。現在、クエリのdate列には制限のないフィルターがあります。行を複数回匿名化することが本当に必要ですか?すでに匿名化されている行の更新やスキャンを回避できますか?日付の範囲をその両側の日付で更新する方が確かに高速です。Anonymised列をインデックスに追加することもできますが、そのインデックスはUPDATEクエリ中に更新する必要があります。要約すると、可能であれば同じデータを何度も処理することは避けてください。

Clustered Index Update演算子で行われた作業のため、並べ替えを使用する元のクエリは遅くなります。インデックスのシークとソートに費やされた時間はわずか407ミリ秒です。これは実際の計画で見ることができます。計画は行モードで実行されるため、ソートに費やされる時間は、すべての子演算子とともにその演算子の時間になります。

ここに画像の説明を入力してください

これにより、並べ替え演算子の時間は約1600ミリ秒になります。SQL Serverは、更新を実行するために、クラスター化インデックスからページを読み取る必要があります。Clustered Index Updateオペレーターが1205921論理読み取りを行っていることがわかります。DMLのソートの最適化と最適化されたプリフェッチの詳細については、Paul Whiteによるこのブログ投稿を参照してください。

他のクエリプラン(並べ替えなし)は、クラスター化インデックススキャンに683ミリ秒、Clustered Index Updateオペレーターに約550ミリ秒かかります。更新演算子は、このクエリに対してIOを実行しません。

並べ替えのあるプランが遅い理由に関する簡単な答えは、SQL Serverがクラスター化インデックススキャンプランと比較して、そのプランのクラスター化インデックスに対してより多くの論理読み取りを行うということです。必要なデータがすべてメモリ内にある場合でも、これらの論理読み取りを行うにはオーバーヘッドとコストがかかります。より良い答えを得るのははるかに困難です。つまり、私が知る限り、この計画では詳細が説明されていません。PerfViewまたはETWトレースに基づく別のツールを使用して、クエリ間のコールスタックを比較できます。

ここに画像の説明を入力してください

左側はクラスター化インデックススキャンを実行するクエリで、右側は並べ替えを実行するクエリです。1つのクエリでのみ表示されるコールスタックを青または赤でマークしました。当然のことながら、並べ替えクエリに対してサンプリングされたCPUサイクル数が多いさまざまな呼び出しスタックは、クラスター化インデックスで更新を実行するために必要な論理読み取りと関係があるようです。さらに、同じ操作のクエリ間では、サンプリングされたサイクル数に違いがあります。サンプルでは、​​ソートを使用したクエリはラッチの取得に31サイクルを費やしていますが、スキャンを使用したクエリはラッチの取得に9サイクルしか費やしていません。

SQL Serverはクエリプランオペレーターのコスト制限により、遅いプランを選択していると思います。おそらく、実行時間の違いの一部は、ハードウェアまたはSQL Serverのエディションによるものです。いずれの場合でも、SQL Serverは、日付列がクラスター化インデックスとまったく同じように暗黙的に順序付けられていることを理解できません。データはクラスター化キースキャンでクラスター化インデックススキャンから返されるため、クラスター化インデックスの更新時にIOを最適化するためにソートを実行する必要はありません。

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