2009-05-22 33 views
11

SQL Server 2005には、約40億行のテーブルがあります。これらの行のうち約20億を削除する必要があります。 1回のトランザクションで試してみると、トランザクションログがいっぱいになり、失敗します。トランザクションログを大きくする余分なスペースはありません。私は最善の方法は、〜10,000のバッチで削除ステートメントをバッチアップすることだと仮定します。SQLバッチ処理の削除

おそらくカーソルを使ってこれを行うことができますが、これを行うための標準的な/簡単な/巧妙な方法ですか?

P.S.この表には、PKとしての識別列はありません。 PKは整数の外部キーと日付で構成されています。

+0

うーん、答えのため –

答えて

7

あなたは「ニブル」は、データベースへの大規模な負荷が生じないことを意味しているのを削除することができます。 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' 
    ) 
+3

そして、2005年現在、実際に行うことができます:TOP(@count)FROM ... –

2

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

私は最良の選択肢は、潜在的なブロッキングの問題を避けるために、1ヒットではなく、より小さなバッチでデータを削除することです。

  • がに戻って一時テーブルから
  • 移動すべてすべてのデータを消去するために、元のテーブルを切り捨て

    1. コピーしたデータは、一時テーブルに保つために:

      また、次の方法を検討することもでき元のテーブル

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

  • +0

    おかげで、私たちはパーティショニングの中で見えたが、私たちはでそれを実装するために、その実用的でなかった...定期的/歴史的なデータのように聞こえます(この問題のために正式にはhttp:// support。microsoft.com/kb/924601)。 一時テーブルへのデータのコピーについて:行を削除するよりも少ないトランザクションログスペースが必要ですか? –

    +0

    DELETE操作を実行する必要がないため、おそらくはいです。表のコピーを作成したら、ソース表をTRUNCATEして、元の表に戻したいデータのみをコピーします。データベースの整合性/リカバリ性を保証するために、すべての操作を実際にログに記録したいので、バッチ削除を実行することをお勧めします。 –

    3

    このようなサウンドは一度の操作です(私はあなたが望みます)。この半分の状態に戻る必要はありません。この場合、単純な単純なトランザクションに切り替えるだけですモードを実行してから終了するとFULLに戻りますか?

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

    あなたのようなSMTとスクリプトでこれを行うことができます。

    ALTER DATABASE myDB SET RECOVERY FULL/SIMPLE 
    

    またセットアップ取引を縮小するために仕事をすることができますが、時間のすべての与えられた時間間隔を記録 - あなたの削除が実行されている間。これはちょっと悪いですが、私はそれがトリックを行うだろうと思う。

    +0

    はい、一度の操作です:) 残念ながら、私たちはすでに単純なリカバリを使用していますが、単純なリカバリでも、1回のトランザクションで削除を実行するとtlog(100GB)がいっぱいになります。 –

    +1

    ここで言及する価値があるのは、シンプルリカバリに切り替えることによってトランザクションバックアップを無効にすることです。それが使用されていない場合、それは問題ありません(実際にはそう多くの方法を使用します)が、それ以外の場合は、トランザクションバックアップを再び使用できるように完全バックアップまたは差分バックアップが必要です。 –

    +0

    「代替」ソリューション/ハックはどうですか? :) – JohnIdol

    8

    削除する行と保存したい行が区別されますか?

    while exists (select 1 from your_table where <your_condition>) 
    delete top(10000) from your_table 
    where <your_condition> 
    
    +0

    where where条件は基本的にWHERE DateTimeInserted

    +0

    トランザクションログをいっぱいにしても、バッチでも削除をログに記録します。 – cjk

    +0

    高価だと思われる場合は選択をスキップできます(単純な終了条件に置き換えるだけです)。トランザクションログの増加に関しては、ループ内のチェックポイントで "truncate on checkpoint"オプションをオンにしていくつかのトリックを行うことができると思います。 –

    -1

    簡単な答えは、何らかの主要なデータベースのダウンタイムを発生させることなく20億行を削除することはできません。

    一時テーブルにデータをコピーして元のテーブルを切り捨てることをお勧めしますが、これはtempDBを満たし、データを削除するよりも少ないログしか使用しません。

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

    2

    私は、一時テーブルの提案に似た何かをするだろうが、私は新しい永久テーブルに残したい行を選択したい、元のテーブルをドロップし、新しい名前を変更します。これは、トランログの影響が比較的少ないはずです。明らかに、名前を変更した後に新しいテーブルに必要なインデックスを再作成することを忘れないでください。

    ちょうど私の2 p'enneth。

    2

    を、あなたはまた、これらのトリックをしようとする場合があります:

    • ドロップ
    • あなたの他の基準に加えて、テーブルからすべてのインデックスをあなたのクラスタ化インデックスの最初の列に一致する条件を追加し、その後、それが可能だとしますないであれば、削除が行われた後に戻ってそれらを置きますあなたのPKがクラスタ化されている場合は、何がDBで起こったが、上記の最初の点に関しては、クラスタ化インデックス

    をKEEPとterfereは、例えば、その後、約あなたがしたい行数と一致する範囲を見つけます各バッチを削除し、それを使用:私は、レコードの小さなセットであなたのループをしたい人に同意

    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 
    
    0

    、これは1つのステップで全体の動作をやろうとしているよりも速くなります。あなたはループ内に含めるべきレコードの数を経験するかもしれません。一度に約2000はテーブルのほとんどのスイートスポットであると思われますが、私はalthouhgから大きなdelteを行いますが、500のような少々の量が必要です。forign keyの数、レコードのサイズ、トリガーなどに依存します。いくつかあなたが必要なものを見つけるために実験。それはテーブルの使用量がどれほど重いかにもよる。頻繁にアクセスされる表は、より短い時間を実行するためにループの各反復を必要とします。営業時間外、またはシングルユーザーモードで実行できる場合は、1つのループでさらに多くのレコードを削除することができます。

    あなたはオフ時間中に一晩でこれを行うにはないと思うなら、それはカウンターでループを設計し、唯一それが行われるまで、毎晩の反復のセット数を行うことをお勧めかもしれません。

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

    これは、この種の操作に着手する直前に、通常、データベースのバックアップには良いアイデアです。ここで

    0

    は私の例です:

    -- configure script 
    -- Script limits - transaction per commit (default 10,000) 
    -- And time to allow script to run (in seconds, default 2 hours) 
    -- 
    DECLARE @MAX INT 
    DECLARE @MAXT INT 
    -- 
    -- These 4 variables are substituted by shell script. 
    -- 
    SET @MAX = $MAX 
    SET @MAXT = $MAXT 
    SET @TABLE = $TABLE 
    SET @WHERE = $WHERE 
    
    -- step 1 - Main loop 
    DECLARE @continue INT 
    -- deleted in one transaction 
    DECLARE @deleted INT 
    -- deleted total in script 
    DECLARE @total INT 
    SET @total = 0 
    DECLARE @max_id INT, @start_id INT, @end_id INT, @interval INT 
    SET @interval = @MAX 
    SELECT @start_id = MIN(id), @max_id = MAX(id) from @TABLE 
    SET @end_id = @start_id + @interval 
    
    -- timing 
    DECLARE @start DATETIME 
    DECLARE @now DATETIME 
    DECLARE @timee INT 
    SET @start = GETDATE() 
    -- 
    SET @continue = 1 
    IF OBJECT_ID (N'EntryID', 'U') IS NULL 
    BEGIN 
        CREATE TABLE EntryID (startid INT) 
        INSERT INTO EntryID(startid) VALUES(@start_id) 
    END 
        ELSE 
    BEGIN 
        SELECT @start_id = startid FROM EntryID 
    END 
    
    
    WHILE (@continue = 1 AND @start_id <= @max_id) 
    BEGIN 
    
        PRINT 'Start issued: ' + CONVERT(varchar(19), GETDATE(), 120) 
        BEGIN TRANSACTION 
         DELETE 
         FROM @TABLE 
         WHERE id BETWEEN @start_id AND @end_id AND @WHERE 
         SET @deleted = @@ROWCOUNT 
        UPDATE EntryID SET EntryID.startid = @end_id + 1 
        COMMIT 
        PRINT 'Deleted issued: ' + STR(@deleted) + ' records. ' + CONVERT(varchar(19), GETDATE(), 120) 
        SET @total = @total + @deleted 
        SET @start_id = @end_id + 1 
        SET @end_id = @end_id + @interval 
        IF @end_id > @max_id 
         SET @end_id = @max_id 
    
        SET @now = GETDATE() 
        SET @timee = DATEDIFF (second, @start, @now) 
        if @timee > @MAXT 
        BEGIN 
        PRINT 'Time limit exceeded for the script, exiting' 
        SET @continue = 0 
        END 
    -- ELSE 
    -- BEGIN 
    --  SELECT @total 'Removed now', @timee 'Total time, seconds' 
    -- END 
    END 
    
    SELECT @total 'Removed records', @timee 'Total time sec' , @start_id 'Next id', @max_id 'Max id', @continue 'COMPLETED? ' 
    SELECT * from EntryID next_start_id 
    
    GO 
    
    +1

    コード自体を超えて説明を追加したい場合があります。 – Akshay

    関連する問題