私はあなたの問題をほとんど再現するテストデータをモックアップしました:
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を最適化するためにソートを実行する必要はありません。