11

SQL Server 2005には、約40億行のテーブルがあります。これらの行のうち約20億行を削除する必要があります。1つのトランザクションで実行しようとすると、トランザクションログがいっぱいになり、失敗します。トランザクションログを大きくするための余分なスペースはありません。前進するための最良の方法は、削除ステートメントをバッチ処理することだと思います(〜10,000のバッチで?)。

私はおそらくカーソルを使用してこれを行うことができますが、これを行うための標準/簡単/賢い方法ですか?

PSこのテーブルには、PKとしてのID列がありません。PKは、整数の外部キーと日付で構成されます。

4

9 に答える 9

10

削除を「かじる」ことができます。これは、データベースに大きな負荷をかけないことも意味します。t-log バックアップが 10 分ごとに実行される場合、これを同じ間隔で 1 回または 2 回実行しても問題ありません。SQL エージェント ジョブとしてスケジュールできます

このようなことを試してください:

DECLARE @count int
SET @count = 10000

    DELETE  FROM table1 
    WHERE table1id IN (
        SELECT TOP (@count) tableid
        FROM table1
        WHERE x='y'
    )
于 2009-05-22T12:06:18.103 に答える
8

削除する行と保持する行の違いは何ですか?これはあなたのために働きますか?

while exists (select 1 from your_table where <your_condition>)
delete top(10000) from your_table
where <your_condition>
于 2009-05-22T08:25:30.810 に答える
5

これを、ログを切り捨てるステートメントを含むバッチに入れることに加えて、次のトリックを試すこともできます。

  • 他の条件に加えて、クラスター化インデックスの最初の列に一致する条件を追加します
  • テーブルからインデックスを削除し、可能であれば削除が完了した後にそれらを元に戻し、DB で行われている他の処理に干渉しないようにしますが、クラスター化されたインデックスは保持します。

上記の最初の点については、たとえば、PK がクラスター化されている場合、各バッチを削除する行数とほぼ一致する範囲を見つけて、それを使用します。

DECLARE @max_id INT, @start_id INT, @end_id INT, @interval INT
SELECT @start_id = MIN(id), @max_id = MAX(id) FROM My_Table
SET @interval = 100000  -- You need to determine the right number here
SET @end_id = @start_id + @interval

WHILE (@start_id <= @max_id)
BEGIN
     DELETE FROM My_Table WHERE id BETWEEN @start_id AND @end_id AND <your criteria>

     SET @start_id = @end_id + 1
     SET @end_id = @end_id + @interval
END
于 2009-05-22T12:15:45.713 に答える
3

これは1回限りの操作のように聞こえますが(私はあなたに願っています)、このバッチ削除の途中の状態に戻る必要はありません-その場合は、実行する前にSIMPLEトランザクションモードに切り替えてみませんか?終わったらFULLに戻りますか?

このようにして、トランザクションログはそれほど大きくなりません。これはほとんどの状況で理想的ではないかもしれませんが、ここでは何も問題はありません(上記のように、削除の間にある状態に戻る必要がないと仮定します)。

次のようなsmtを使用して、スクリプトでこれを行うことができます。

ALTER DATABASE myDB SET RECOVERY FULL/SIMPLE

または、削除の実行中に、指定された時間間隔ごとにトランザクションログを縮小するジョブを設定できます。これはちょっと悪いですが、私はそれがトリックをするだろうと思います。

于 2009-05-22T08:18:10.967 に答える
2

たとえば、日付列に基づいてSQL Serverパーティションを使用している場合は、不要になったパーティションを切り替えた可能性があります。おそらく将来の実装に関する考慮事項。

あなたが言うように、潜在的なブロッキングの問題を回避するために、データを1回のヒットではなく、より小さなバッチで削除するのが最善のオプションだと思います。

次の方法も検討できます。

  1. データをコピーして一時テーブルに保持します
  2. 元のテーブルを切り捨てて、すべてのデータをパージします
  3. すべてを一時テーブルから元のテーブルに戻します

データが元のテーブルに追加されたときに、インデックスも再構築されます。

于 2009-05-22T08:11:44.507 に答える
2

一時テーブルの提案と同様のことを行いますが、保持したい行を新しい永続テーブルに選択し、元のテーブルを削除してから、新しいテーブルの名前を変更します。これにより、トランザクション ログへの影響は比較的少なくなります。名前を変更した後、新しいテーブルに必要なインデックスを再作成することを忘れないでください。

ちょうど私の 2 ペンス。

于 2009-05-22T10:32:54.487 に答える
0

少数のレコードセットをループすることを望んでいる人々に同意します。これは、操作全体を1つのステップで実行しようとするよりも高速です。ループに含める必要のあるレコードの数を体験することができます。一度に約2000が、ほとんどのテーブルのスイートスポットのようです。私が行う大規模なデルテは、500などの少量が必要なものもあります。外部キーの数、レコードのサイズ、トリガーなどによって異なります。あなたが必要なものを見つけるためにいくつかの実験。それはまた、テーブルの使用がどれほど重いかにも依存します。頻繁にアクセスされるテーブルでは、実行時間を短縮するためにループを繰り返す必要があります。営業時間外に実行できる場合、またはシングルユーザーモードで実行できる場合は、1つのループでより多くのレコードを削除できます。

営業時間外のある夜にこれを行うとは思わない場合は、カウンターを使用してループを設計し、完了するまで毎晩設定された回数の反復のみを行うのが最善の場合があります。

さらに、明示的なトランザクションではなく暗黙的なトランザクションを使用する場合は、いつでもループクエリを強制終了でき、ループの現在のラウンドのレコードを除いて、すでに削除されたレコードは削除されたままになります。システムを停止させたため、50万レコードをロールバックしようとするよりもはるかに高速です。

通常、この種の操作を行う直前にデータベースをバックアップすることをお勧めします。

于 2009-05-22T14:01:29.493 に答える
0

簡単に言えば、何らかの大規模なデータベースのダウンタイムを発生させずに 20 億行を削除することはできないということです。

最適なオプションは、データを一時テーブルにコピーして元のテーブルを切り捨てることかもしれませんが、これにより tempDB がいっぱいになり、データを削除するのと同じようにログを使用することになります。

トランザクション ログがいっぱいになるまでできるだけ多くの行を削除し、その後は毎回切り詰める必要があります。Stanislav Kniazev によって提供された回答は、バッチ サイズを増やし、ログ ファイルを切り捨てるための呼び出しを追加することで、これを行うように変更できます。

于 2009-05-22T08:41:29.750 に答える