以下で説明するように、クエリに基づいて非常に大きな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
上記のロジックで何百万ものレコードを削除する場合はどうでしょうか。その長所と短所。
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])
_
ただし、インデックスを追加すると、この新しく追加されたインデックスから行を削除するために追加のソートが表示されます。
そして、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行のみを返すことができます。
実行時間を改善しました:
_ 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を返しますが、これは最適ではありません。
実行時間が長い場合:
_ SQL Server Execution Times:
CPU time = 27717 ms, elapsed time = 10657 ms.
_
しかし、これはあなたのデータに関するより多くの情報なしには本当に意味がありません。より正確なアドバイスを提供するには、クエリプランが必要です。
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 を参照)。
上記のクエリは、インデックスが見つからないために正常に実行される可能性がありますが、クエリはまだ間違っています。
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秒かかりました。
上記のようにインデックスを作成すると、間違いなく最高のパフォーマンスが得られます。
Where
条件にSub Query
がないため、複雑なクエリでHigh Cardianility Estimate
が発生する可能性があります。index
よりもOptimize query
の記述を重視してください。どちらも重要です。注:
Parameter Sniffing
が原因でパフォーマンスが低下または悪化している場合は、Parameter sniffing
を回避するための適切な方法を見つける必要があります。それ以外の場合は、無視する必要があります。
結局、すべてのStore Procedure
がOPTION RECOMPILE
で書かれているわけではありません。
私の理解する限り、私のスクリプトでは@FromDate
および@Todate
はprocパラメータではなく、ローカル変数なので、Parameter Sniffing
の質問はありません。