web-dev-qa-db-ja.com

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

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

このクエリに最適な方法は何ですか

   Delete LargeTable where readTime < dateadd(MONTH,-7,GETDATE())
98
user3107343
  1. そのテーブルのすべての行を削除する場合、最も簡単なオプションはテーブルの切り捨てです。

    TRUNCATE TABLE LargeTable
    GO
    

    切り捨てテーブルは単にテーブルを空にするでしょう、あなたは削除される行を制限するためにWHERE句を使用することができず、トリガーは起動されません。

  2. 一方、80〜90パーセントを超えるデータを削除する場合、合計1,100万行あり、別の方法で1000万行を削除する場合は、これらの100万行を挿入することになります。 )別のステージングテーブルに移動します。この大きなテーブルを切り捨てて、これらの100万行を挿入します。

  3. あるいは、この大きなテーブルを基になるテーブルとして持つパーミッション/ビューまたは他のオブジェクトがこのテーブルを削除しても影響を受けない場合は、これらの比較的少量の行を別のテーブルにドロップして同じテーブルを作成してインポートこのex-Largeテーブルに行を戻します。

  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
    

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

158
M.Ali

@ 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
66

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

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

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
9
Ken Koehler

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

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
8
Cassio Veras

パーティショニングを実装する意思がある(そして可能である)場合は、ランタイムオーバーヘッドをほとんど発生させずに大量のデータを削除するのに効果的な手法です。ただし、一回限りの運動では費用対効果は高くありません。

7
Michael Green

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

このテーブルに自動インクリメントの主キーがある場合は、この主キーを使用できます。

  1. ReadTime <dateadd(MONTH、-7、GETDATE())の場合、ラージテーブルの主キーの最小値を取得します。 (readTimeにインデックスを追加します。まだ存在していない場合は、このインデックスはとにかくステップ3のテーブルと共に削除されます)。変数 'min_primary'に保存しましょう。

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

  3. 大きなテーブルを削除します。

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

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

4
Arpan Jain

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
3

もう一つの用途:

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
Ali Osman Yavuz

短い構文

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

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

(PARTITIONS({|} [、... n]))で表を切り捨てます

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

欠点は、テーブルにパーティションが設定されていない場合は、通常の方法で古い学校に行ってデータを削除してから、パーティションを作成してテーブルを再作成する必要があることです。挿入手順自体にパーティションの作成と削除を追加しました。私は5億行のテーブルを持っていたので、これが削除時間を減らす唯一の選択肢でした。

詳細については、以下のリンクを参照してください。 https://docs.Microsoft.com/ja-jp/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()
1