web-dev-qa-db-ja.com

SQL Serverテーブルでの巨大な削除操作のチューニング

以下で説明するように、クエリに基づいて非常に大きなSQLサーバーテーブルで削除操作を実行しています。

delete db.st_table_1
where service_date between(select min(service_date) from stg_table)
                   and (select max(service_date) from stg_table);

stg_tableとstg_table_1にはservice_dateのインデックスがありません。

これらのテーブルには両方とも100万行のデータがロードされており、削除操作には多くの時間がかかります。このクエリのパフォーマンスを改善するための提案をリクエストします。

下記の質問に記載されている戦略を参照しましたが、その実装方法を理解できませんでした。

SQL Serverでデータを失うことなく大量のデータを削除するには?

これに関するあなたの親切な提案を要求します。

更新:

select * into db.temp_stg_table_1
from db.stg_table_1
where service_date not between( select min(service_date) from db.stg_table)
                             and (select max(service_date) from db.stg_table);

exec sp_rename 'stg_table_1' , 'stg_table_1_old'

exec sp_rename 'temp_stg_table_1' , 'test_table_1'

drop table stg_table_1_old

上記のロジックで何百万ものレコードを削除する場合はどうでしょうか。その長所と短所。

3
vikrant rana

コメントに基づくテスト

SQL Server 2014 SP3でテスト済み

stg_tableとstg_table_1にはservice_dateのインデックスがありません。

これらのテーブルの両方に100万行のデータが読み込まれ、削除操作に多くの時間がかかります。

[〜#〜] ddl [〜#〜]

_CREATE TABLE dbo.st_table_1( stg_table_1_ID INT IDENTITY(1,1) PRIMARY KEY NOT NULL,
                             service_date datetime2,
                            val  int)
CREATE TABLE dbo.stg_table (stg_table_ID INT IDENTITY(1,1) PRIMARY KEY NOT NULL,
                            service_date datetime2,
                            val  int)
_

IDフィールドのPK +クラスタ化インデックス。

[〜#〜] dml [〜#〜]

_INSERT INTO dbo.stg_table WITH(TABLOCK)
(
service_date,val) 
SELECT -- 1M
 DATEADD(S,rownum,GETDATE()),rownum
 FROM
 (SELECT TOP(1000000) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2) as sptvalues

INSERT INTO dbo.st_table_1 WITH(TABLOCK)
(
service_date,val) 
SELECT -- 2.5M
 DATEADD(S,rownum,GETDATE()),rownum
 FROM
 (SELECT TOP(2500000) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2) as sptvalues

INSERT INTO dbo.stg_table WITH(TABLOCK)
(
service_date,val) 
SELECT -- 4M
 DATEADD(S,rownum,GETDATE()),rownum
 FROM
 (SELECT TOP(4000000) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2) as sptvalues
_

_dbo.st_table_1_の2.5M行と_dbo.stg_table_の5M行(ほとんど)これらの2.5M行はすべて、クエリによって削除されます。これは、ユーザーの10分の1未満です。

クエリの実行

実際の実行計画 ベースの削除ステートメント

予想どおり、_dbo.stg_table_に2回アクセスして、ストリーム集約で最大値と最小値を取得します。 CPU時間と経過時間/実行時間:

_  CPU time = 4906 ms,  elapsed time = 4919 ms.
_

欠落しているインデックスヒントが実行プランに追加されます。

_CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[st_table_1] ([service_date])
INCLUDE ([stg_table_1_ID])
_

ただし、インデックスを追加すると、この新しく追加されたインデックスから行を削除するために追加のソートが表示されます。

enter image description here

計画

そして、CPU時間/経過時間が増加します:

_   CPU time = 11156 ms,  elapsed time = 11332 ms.
_

[〜#〜] ymmv [〜#〜]ですが、私の例では、データに関するコメントに基づいて、クエリを改善しませんでした。

_[dbo].[stg_table]_でインデックスを作成する

_CREATE NONCLUSTERED INDEX IX_service_date
ON [dbo].[stg_table] ([service_date]);
_

その結果、MAX()およびMIN()は、新しく作成されたインデックスを利用して、完全なクラスター化インデックススキャンではなく1行のみを返すことができます。

enter image description here

実行時間を改善しました:

_ SQL Server Execution Times:
   CPU time = 2609 ms,  elapsed time = 4028 ms.
_

そして 実行計画

しかし、これはインデックス作成と私自身の例にのみ基づいています。ご自身の責任で行ってください。


補足ノート

削除を個別のバッチに分割して、ログファイルがいっぱいになったり、削除の失敗/成功の大きなブロックが1つもないようにする必要があります。

また、_(TABLOCK)_を使用して、テーブル全体が最初からロックされるようにすることもできます。

_SET STATISTICS IO, TIME ON;
delete dbo.st_table_1 WITH(TABLOCK)
where service_date between(select min(service_date) from stg_table)
                   and (select max(service_date) from stg_table);
_

更新:_SELECT INTO_ + _sp_rename_

_select * into db.temp_stg_table_1
from db.stg_table_1
where service_date not between( select min(service_date) from db.stg_table)
                             and (select max(service_date) from db.stg_table);

exec sp_rename 'stg_table_1' , 'stg_table_1_old'

exec sp_rename 'temp_stg_table_1' , 'test_table_1'

drop table stg_table_1_old
_

上記のロジックで何百万ものレコードを削除する場合はどうでしょうか。その長所と短所。

パフォーマンスとは別に、_sp_rename_を完了するには_Sch-M_ロックが必要です。つまり、変更する前に、他のすべてのセッションがテーブルのロックを解放するのを待つ必要があります。元のテーブルのインデックス/制約はすべてなくなり、それらを再作成する必要があります。

自分のデータに対してクエリを実行すると:

_select * into dbo.temp_stg_table_1
from dbo.st_table_1
where service_date not between( select min(service_date) from dbo.stg_table)
                             and (select max(service_date) from dbo.stg_table);
_

これはデータを表すものではありません。覚えておいてください。

すべての行を読み取って0を返しますが、これは最適ではありません。

enter image description here

実行時間が長い場合:

_ SQL Server Execution Times:
   CPU time = 27717 ms,  elapsed time = 10657 ms.
_

しかし、これはあなたのデータに関するより多くの情報なしには本当に意味がありません。より正確なアドバイスを提供するには、クエリプランが必要です。

6
Randi Vertongen

1つのステートメントで3,700万行を削除することはありません 。これは、取得する実行プランに関するものではありません。削除する行を検索するオーバーヘッド(パラメータースニッフィングがそれらの行の検索に影響するかどうかに関係なく)は、実際にそれらを削除してそれらの削除をログに記録するオーバーヘッドよりもはるかに低くなります。これをチャンクに分割すると、時間の経過とともにそのコストを償却し、一度にすべてではなく、好みに合ったスケジュールで削除を処理できます。

-- you can play with these parameters to see what offers the best trade-off
DECLARE @BatchSize int = 10000, @TransactionInterval tinyint = 5;

DECLARE @s datetime, @e datetime, @r int = 1;

SELECT @s = MIN(service_date), @e = MAX(service_date) FROM dbo.stg_table;

BEGIN TRANSACTION;

WHILE (@r > 0)
BEGIN
  IF @r % @TransactionInterval = 1
  BEGIN
    COMMIT TRANSACTION;
    BEGIN TRANSACTION;
  END

  DELETE TOP (@BatchSize) FROM db.st_table_1
    WHERE service_date >= @s AND service_date <= @e;

  SET @r = @@ROWCOUNT;
END

IF @@TRANCOUNT > 0
BEGIN
  COMMIT TRANSACTION;
END

最新バージョンのSQL Serverを使用している場合は、持続性の遅延を検討することもできます( this answer および this blog post を参照)。

1
Aaron Bertrand

上記のクエリは、インデックスが見つからないために正常に実行される可能性がありますが、クエリはまだ間違っています。

Declare @Fromdate DateTime
Declare @Todate DateTime

select @Fromdate=min(service_date),@Todate=max(service_date) 
from dbo.stg_table

SET STATISTICS IO, TIME ON;
delete dbo.st_table_1 WITH(TABLOCK)
where service_date >=@Fromdate
                  and service_date <=@Todate

上記の例を使用して、インデックスなしで実行しました。410792行を削除するには18秒かかりました。

上記のようにインデックスを作成すると、間違いなく最高のパフォーマンスが得られます。

  1. したがって、Where条件にSub Queryがないため、複雑なクエリでHigh Cardianility Estimateが発生する可能性があります。
  2. indexよりもOptimize queryの記述を重視してください。どちらも重要です。

注:

Parameter Sniffingが原因でパフォーマンスが低下または悪化している場合は、Parameter sniffingを回避するための適切な方法を見つける必要があります。それ以外の場合は、無視する必要があります。

結局、すべてのStore ProcedureOPTION RECOMPILEで書かれているわけではありません。

私の理解する限り、私のスクリプトでは@FromDateおよび@Todateはprocパラメータではなく、ローカル変数なので、Parameter Sniffingの質問はありません。

0
KumarHarsh