ログなしでSQLでテーブルの大きなデータを削除するにはどうすればよいですか?


127

大きなデータテーブルがあります。このテーブルには1000万のレコードがあります。

このクエリの最良の方法は何ですか

   Delete LargeTable where readTime < dateadd(MONTH,-7,GETDATE())

4
:)すべての行をreadTime> = dateadd(MONTH、-7、GETDATE())にして別のテーブルに入れてからTruncateテーブルを発行し、ETLを使用してデータを戻す何らかのETLを書くつもりがない限り、私は恐れています、ログへの書き込みを防ぐことはできません
TMNT2014

ロギングは、回復力のあるトランザクションを実現するためのオールオアナッシング機能です。ある操作に対してはログがなく、他の操作に対してはないことは、文字通り意味がありません。そうでなければ、ログは役に立ちません。
エリックフィリップス

1
保持するデータをエクスポートし、テーブルをトランケートしてから、インポートし直します
ボヘミアン

別のオプションは、ログに記録されないtablevariableを使用することです。したがって、readTime> = dateadd(MONTH、-7、GETDATE())データをテーブル変数に格納し、元のテーブルを切り捨てて、テーブル変数からデータをコピーします。ただし、何か問題が発生し、テーブルが誤って切り捨てられた場合に備えて、データのバックアップを保持します。:)常に、より少ない環境でスクリプトのテスト実行を行ってください。
TMNT2014 2014年

回答:


203
  1. そのテーブルのすべての行を削除する場合、最も簡単なオプションは次のようにテーブルをトランケートすることです

    TRUNCATE TABLE LargeTable
    GO

    テーブルをトランケートするとテーブルが空になるだけで、WHERE句を使用して削除される行を制限することはできず、トリガーは起動されません。

  2. 一方、データの80〜90%を削除する場合、たとえば、合計1100万行あり、1千万行を削除する場合は、これらの100万行を挿入することになります(保持するレコード) )別のステージングテーブル。この大きなテーブルをトランケートし、これらの100万行を挿入します。

  3. または、アクセス許可/ビュー、または基になるテーブルとしてこの大きなテーブルを持つ他のオブジェクトが、このテーブルをドロップしても影響を受けない場合は、これらの比較的少量の行を別のテーブルに取得して、このテーブルをドロップし、同じスキーマで別のテーブルを作成してインポートすることができます。この元の大規模なテーブルに戻る行。

  4. 私が考えることができる最後のオプションの1つは、データベースを変更してからRecovery Mode to SIMPLE、次のようなwhileループを使用して小さなバッチで行を削除することです。

    DECLARE @Deleted_Rows INT;
    SET @Deleted_Rows = 1;
    
    
    WHILE (@Deleted_Rows > 0)
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (10000)  LargeTable 
         WHERE readTime < dateadd(MONTH,-7,GETDATE())
    
      SET @Deleted_Rows = @@ROWCOUNT;
    END

回復モードを完全に戻すことを忘れないでください。完全に効果を上げるには、バックアップを取る必要があると思います(変更または回復モード)。


14
また、テーブルを切り捨てると、それに関連付けられたFKを使用できなくなることに注意してください。
HLGEM 2015

1
しかし、データの80-90%を確実に削除する方法は?削除する必要がある値の範囲のみがあると仮定しましょう。そして、私はいくつかのテーブルを持っています。だから私はそれらすべてをチェックしてパーセンテージを計算する必要があります、そしてそれが約30%なら私はこの方法はあまり効果的ではないと思います...私は未知のケースの最適な解決策を見つけようとしています。
Archont 2016年

7
optimal solution for unknown case夢である@Archont ですね。残念ながら、1つの薬ですべての病気を治すことはできません。さまざまなシナリオに対していくつかの可能な解決策を提案しました。残念ながら、ここには細長い弾丸はありません。
M.Ali 2016年

5
オプション4を選択する場合の注意点:テーブルの使用方法によっては、一度に5000行未満を削除してロックのエスカレーションを回避する方が適切な場合があります。
Daniel

削除するレコードの数がはるかに多い場合、テーブルに残るレコードは、元のテーブルにあるレコードの一時テーブルに単純に選択してドロップし、一時テーブルの名前を変更する方がはるかに高速であることがわかりました。どこかにID Id外部キーを使用しないことを考えると、
Vladimir Bozic 2017

95

@ m-aliの答えは正しいですが、各チャンクの後にトランザクションをコミットしてチェックポイントを実行しないと、ログが大きくなる可能性があることにも注意してください。これは私がそれを行う方法であり、パフォーマンステストとグラフを使用して、この記事http://sqlperformance.com/2013/03/io-subsystem/chunk-deletesを参照として取得します。

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;


WHILE (@Deleted_Rows > 0)
  BEGIN

   BEGIN TRANSACTION

   -- Delete some small number of rows at a time
     DELETE TOP (10000)  LargeTable 
     WHERE readTime < dateadd(MONTH,-7,GETDATE())

     SET @Deleted_Rows = @@ROWCOUNT;

   COMMIT TRANSACTION
   CHECKPOINT -- for simple recovery model
END

1
これは、使用可能なディスク容量が限られている場合に受け入れられる答えです。なしCOMMIT TRANSACTIONCHECKPOINTログはまだ成長しています。これを明確にしていただきありがとうございます。
gkoul

+1。@Deleted_Rows10000 と比較したい場合や、小さなデータセットを無期限に削除するために無限ループになる場合があることに注意してください。したがってWHILE (@Deleted_Rows = 10000)、削除するデータの完全な「ページ」がなくなるとすぐに停止します。実装でWHILE (@Deleted_Rows > 0)は、whileループは1行しか削除していなくても再度実行され、次の実行でも削除する行が1つまたは2つ見つかる可能性があり、無限ループが発生します。
NS du Toit

@NSduToit WHERE句は、少なくとも7か月前のレコードを考慮しているため、削除の実行中にその条件を満たす新しいレコードはありません。
サンフランシスコゴールデンスタイン

@FranciscoGoldensteinさて、WHILEループ自体の中で日付を繰り返し計算するため、クエリで使用される日付は反復ごとに異なりますdateadd(MONTH,-7,GETDATE())
NS du Toit

@FranciscoGoldensteinまた、おそらくこれ以外のユースケースのために-新しいデータが基になるテーブルに追加され、WHILEループの異なる反復間で削除できる新しいレコードが生成される可能性があります。
NS du Toit

52

GO +を使用して、同じクエリを何回実行するかを指定することもできます。

DELETE TOP (10000)  [TARGETDATABASE].[SCHEMA].[TARGETTABLE] 
WHERE readTime < dateadd(MONTH,-1,GETDATE());
-- how many times you want the query to repeat
GO 100

私はこれが好きです、私が誤って同じ行をテーブルに誤って2600万回挿入し、それをすべて削除する必要があったため、1つのdeleteステートメントでサーバーのメモリが不足したため、これは素晴らしい質問です、削除する行が不足するとループの途中で停止しますか?
ScottC 2017年

2
@ScottC、これはループではなく、クエリを繰り返すだけで(バッチのように)、行が不足すると何も削除できません。しかし、それは止まりません。削除した行がなくなると、(0行が影響を受ける)のような結果になります。
バンカーバスター2017年

ああ、はい、質問を投稿してから約5分後に削除が完了したので発見しました。これは非常に役に立ちました。
ScottC 2017年

1
この構文はどのMS SQL Server GO xxから機能するはずですか?私は取得「ストアドプロシージャが見つかりませんでした『』」エラーを。GOコマンドがなくても問題なく動作します。
アベル

3
うーん、私はそれを実行できるようで、実際に複数回実行されますが、MS SQL Mgt Studioでは、前述のエラーで赤い巻き線が表示されます(ただし、F5-runは機能します)
Abel

11

@Francisco Goldenstein、ちょっとした修正。変数を設定した後でCOMMITを使用する必要があります。そうしないと、WHILEは一度だけ実行されます。

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;

WHILE (@Deleted_Rows > 0)
BEGIN
    BEGIN TRANSACTION

    -- Delete some small number of rows at a time
    DELETE TOP (10000)  LargeTable 
    WHERE readTime < dateadd(MONTH,-7,GETDATE())

    SET @Deleted_Rows = @@ROWCOUNT;

    COMMIT TRANSACTION
    CHECKPOINT -- for simple recovery model

END

10

M.Aliのこのバリエーションは、私にとってはうまく機能しています。一部を削除し、ログをクリアして繰り返します。丸太の成長、落下、やり直しを監視しています。

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;
WHILE (@Deleted_Rows > 0)
  BEGIN
   -- Delete some small number of rows at a time
    delete top (100000) from InstallLog where DateTime between '2014-12-01' and '2015-02-01'
    SET @Deleted_Rows = @@ROWCOUNT;
    dbcc shrinkfile (MobiControlDB_log,0,truncateonly);
END

これはとても役に立ちました!一度# of rowsに削除するとWHERE句もパラメータ化するように変更しました。魅力的な作品!
シヴァ

7

パーティショニングを実装する意思がある(そして可能である)場合、それは実行時のオーバーヘッドがほとんどなく大量のデータを削除するための効果的な手法です。ただし、1回限りの演習では費用対効果が高くありません。


4

2100万行のテーブルから1900万行をほんの数分で削除できました。これが私のアプローチです。

あなたが持っている場合は自動インクリメントの主キーこのテーブルの上に、あなたはこのプライマリキーを利用することができます。

  1. readTime <dateadd(MONTH、-7、GETDATE())である大きなテーブルの主キーの最小値を取得します。(readTimeにインデックスを追加します(まだ存在しない場合)。このインデックスは、手順3のテーブルとともに削除されます)。変数 'min_primary'に保存しましょう

  2. 主キー> min_primaryを持つすべての行をステージングテーブル(行数が大きくない場合はメモリテーブル)に挿入します。

  3. 大きなテーブルをドロップします。

  4. テーブルを再作成します。すべての行をステージングテーブルからメインテーブルにコピーします。

  5. ステージングテーブルを削除します。


3

次のようなwhileループを使用して小さなバッチを削除できます。

DELETE TOP (10000)  LargeTable 
WHERE readTime < dateadd(MONTH,-7,GETDATE())
WHILE @@ROWCOUNT > 0
BEGIN
    DELETE TOP (10000)  LargeTable 
    WHERE readTime < dateadd(MONTH,-7,GETDATE())
END

2

別の用途:

SET ROWCOUNT 1000 -- Buffer

DECLARE @DATE AS DATETIME = dateadd(MONTH,-7,GETDATE())

DELETE LargeTable  WHERE readTime < @DATE
WHILE @@ROWCOUNT > 0
BEGIN
   DELETE LargeTable  WHERE readTime < @DATE
END
SET ROWCOUNT 0

オプション。

トランザクションログが有効になっている場合は、トランザクションログを無効にします。

ALTER DATABASE dbname SET RECOVERY SIMPLE;

2

短い構文

select 1
WHILE (@@ROWCOUNT > 0)
BEGIN
  DELETE TOP (10000) LargeTable 
  WHERE readTime < dateadd(MONTH,-7,GETDATE())
END

1

SQL Server 2016以降を使用していて、テーブルに削除しようとしている列(タイムスタンプ列など)に基づいて作成されたパーティションがある場合、この新しいコマンドを使用して、パーティションごとにデータを削除できます。

(PARTITIONS({|} [、... n]))を含む表の切り捨て

これにより、選択したパーティションのデータのみが削除され、トランザクションログが作成されず、通常の切り捨てと同じ速さで実行されますが、すべてのデータが削除されないため、テーブルの一部からデータを削除する最も効率的な方法です。テーブルから。

欠点は、テーブルにパーティションが設定されていない場合、古い方法でデータを削除し、通常の方法でデータを削除してから、パーティションでテーブルを再作成して、将来これを実行できるようにする必要があります。パーティションの作成と削除を挿入手順自体に追加しました。5億行のテーブルがあったので、これが削除時間を短縮する唯一のオプションでした。

詳細については、以下のリンクを参照してください。https//docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view = sql-server-2017

SQL Server 2016パーティションのあるテーブルを切り捨て

以下は、必要なデータを含むパーティションを含むテーブルを再作成する前に、最初にデータを削除するために行ったものです。このクエリは、データが削除されるまで、指定された時間枠の数日間実行されます。

:connect <<ServerName>>
use <<DatabaseName>>

SET NOCOUNT ON;
DECLARE @Deleted_Rows INT;
DECLARE @loopnum INT;
DECLARE @msg varchar(100);
DECLARE @FlagDate datetime;
SET @FlagDate =  getdate() - 31;
SET @Deleted_Rows = 1;
SET @loopnum = 1;

/*while (getdate() < convert(datetime,'2018-11-08 14:00:00.000',120))
BEGIN
    RAISERROR( 'WAIT for START' ,0,1) WITH NOWAIT   
    WAITFOR DELAY '00:10:00'
END*/
RAISERROR( 'STARTING PURGE' ,0,1) WITH NOWAIT   

WHILE (1=1)
BEGIN
    WHILE (@Deleted_Rows > 0 AND (datepart(hh, getdate() ) >= 12 AND datepart(hh, getdate() ) <= 20)) -- (getdate() < convert(datetime,'2018-11-08 19:00:00.000',120) )
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (500000)  dbo.<<table_name>>
         WHERE timestamp_column < convert(datetime, @FlagDate,102)
         SET @Deleted_Rows = @@ROWCOUNT;
         WAITFOR DELAY '00:00:01'
         select @msg = 'ROWCOUNT' + convert(varchar,@Deleted_Rows);
         set @loopnum = @loopnum + 1
         if @loopnum > 1000
             begin 
                 begin try
                        DBCC SHRINKFILE (N'<<databasename>>_log' , 0, TRUNCATEONLY)
                        RAISERROR( @msg ,0,1) WITH NOWAIT
                 end try
                 begin catch
                     RAISERROR( 'DBCC SHRINK' ,0,1) WITH NOWAIT  
                 end catch
                 set @loopnum = 1
             end
        END
WAITFOR DELAY '00:10:00'
END 
select getdate()

0

ループなしで言うと、GOTOSQLサーバーを使用して大量のレコードを削除するステートメントを使用できます。エクサ。

 IsRepeat:
    DELETE TOP (10000)
    FROM <TableName>
    IF @@ROWCOUNT > 0
         GOTO IsRepeat

このように、サイズの小さい削除で大量のデータを削除できます。

さらに情報が必要な場合はお知らせください。

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