sql >> データベース >  >> RDS >> Sqlserver

SQL Serverで数百万行の大きなテーブルを更新するにはどうすればよいですか?

    1. 操作がページロックを取得していることが確実でない限り、セット内の10k行を更新しないでください(ページごとに複数の行がUPDATEの一部であるため) 手術)。問題は、ロックのエスカレーション(行またはページからテーブルへのロック)が5000ロックで発生することです。 。したがって、操作で行ロックを使用している場合に備えて、5000未満に保つのが最も安全です。

    2. すべきではありません SET ROWCOUNTを使用して、変更される行の数を制限します。ここには2つの問題があります:

      1. SQL Server 2005がリリースされてから(11年前)、廃止されました:

        SET ROWCOUNTを使用しても、SQL Serverの将来のリリースでは、DELETE、INSERT、およびUPDATEステートメントには影響しません。新しい開発作業では、DELETE、INSERT、およびUPDATEステートメントでSET ROWCOUNTを使用することは避け、現在それを使用しているアプリケーションを変更することを計画してください。同様の動作については、TOP構文を使用してください

      2. それはあなたが扱っているステートメント以上のものに影響を与える可能性があります:

        SET ROWCOUNTオプションを設定すると、ほとんどのTransact-SQLステートメントは、指定された行数の影響を受けたときに処理を停止します。これにはトリガーが含まれます。 ROWCOUNTオプションは動的カーソルには影響しませんが、キーセットおよび非依存カーソルの行セットを制限します。このオプションは注意して使用する必要があります。

      代わりに、TOP ()を使用してください 条項。

    3. ここで明示的なトランザクションを行うことに目的はありません。コードが複雑になり、ROLLBACKを処理できません。 、各ステートメントは独自のトランザクション(つまり、自動コミット)であるため、これも必要ありません。

    4. 明示的なトランザクションを維持する理由を見つけたとすると、TRYはありません。 / CATCH 構造。 TRYについては、DBA.StackExchangeで私の回答をご覧ください。 / CATCH トランザクションを処理するテンプレート:

      トランザクションをC#コードとストアドプロシージャで処理する必要がありますか

    本当のWHERE 質問のサンプルコードには句が表示されていないため、表示されている内容に依存するだけで、より良い モデルは次のようになります:

    DECLARE @Rows INT,
            @BatchSize INT; -- keep below 5000 to be safe
        
    SET @BatchSize = 2000;
    
    SET @Rows = @BatchSize; -- initialize just to enter the loop
    
    BEGIN TRY    
      WHILE (@Rows = @BatchSize)
      BEGIN
          UPDATE TOP (@BatchSize) tab
          SET    tab.Value = 'abc1'
          FROM  TableName tab
          WHERE tab.Parameter1 = 'abc'
          AND   tab.Parameter2 = 123
          AND   tab.Value <> 'abc1' COLLATE Latin1_General_100_BIN2;
          -- Use a binary Collation (ending in _BIN2, not _BIN) to make sure
          -- that you don't skip differences that compare the same due to
          -- insensitivity of case, accent, etc, or linguistic equivalence.
    
          SET @Rows = @@ROWCOUNT;
      END;
    END TRY
    BEGIN CATCH
      RAISERROR(stuff);
      RETURN;
    END CATCH;
    

    @Rowsをテストする @BatchSizeに対して 、その最後のUPDATEを回避できます 最終的なセットは通常、@BatchSizeよりも少ない行数であるため、クエリ(ほとんどの場合) 、この場合、処理するものがこれ以上ないことがわかります(これは、回答に示されている出力に表示されます)。行の最終セットが@BatchSizeに等しい場合のみ このコードは最終的なUPDATEを実行しますか 0行に影響します。

    WHEREにも条件を追加しました すでに更新されている行が再度更新されないようにする句。

    パフォーマンスに関する注意

    上記で「より良い」を強調しました(「これはより良い」のように) モデル」)これは、O.P。の元のコードに比べていくつかの改善があり、多くの場合は正常に機能しますが、すべての場合に完全ではないためです。少なくとも特定のサイズのテーブルの場合(いくつかの要因によって異なるため、具体的には、次のいずれかの場合に修正する行が少なくなるため、パフォーマンスが低下します。

    1. クエリをサポートするインデックスがない、または
    2. インデックスがありますが、WHEREに少なくとも1つの列があります 句は、バイナリ照合を使用しない文字列データ型であるため、COLLATE ここで句がクエリに追加され、バイナリ照合が強制されます。これにより、インデックスが無効になります(この特定のクエリの場合)。

    これは@mikesigsが遭遇した状況であるため、別のアプローチが必要です。更新されたメソッドは、更新されるすべての行のIDを一時テーブルにコピーし、その一時テーブルを使用してINNER JOIN クラスタ化インデックスキー列で更新されるテーブルに。 (クラスター化インデックスをキャプチャして参加することが重要です 列、それらが主キー列であるかどうかに関係なく!)

    詳細については、以下の@mikesigsの回答を参照してください。その答えに示されているアプローチは、私が何度も使用した非常に効果的なパターンです。私が行う唯一の変更は次のとおりです。

    1. #targetIdsを明示的に作成します SELECT INTO...を使用するのではなくテーブル
    2. #targetIdsの場合 テーブルで、列にクラスター化された主キーを宣言します。
    3. #batchIdsの場合 テーブルで、列にクラスター化された主キーを宣言します。
    4. #targetIdsに挿入する場合 、INSERT INTO #targetIds (column_name(s)) SELECTを使用します および ORDER BYを削除します 不要なので。

    したがって、この操作に使用できるインデックスがなく、実際に機能するインデックスを一時的に作成できない場合(WHEREによっては、フィルタリングされたインデックスが機能する場合があります。 UPDATEの句 クエリ)、@ mikesigsの回答に示されているアプローチを試してください(そのソリューションを使用する場合は、賛成票を投じてください)。



    1. postgresのUPDATERETURNING句から選択できません

    2. mysql_num_rows():指定された引数は有効なMySQL結果リソースではありません

    3. MySQL5.7でネイティブパスワードを使用する方法

    4. パフォーマンスの驚きと仮定:DATEDIFF