このwhileループで明示的なトランザクションが必要ですか?


11

SQL Server 2014:

非常に大きな(1億行)テーブルがあり、その上のいくつかのフィールドを更新する必要があります。

ログシッピングなどの場合も、一口サイズのトランザクションを維持する必要があります。

以下を少し実行してからクエリをキャンセル/終了すると、これまでに行われた作業はすべてコミットされますか、いつでもキャンセルできるように明示的なBEGIN TRANSACTION / END TRANSACTIONステートメントを追加する必要がありますか?

DECLARE @CHUNK_SIZE int
SET @CHUNK_SIZE = 10000

UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
where deleted is null or deletedDate is null

WHILE @@ROWCOUNT > 0
BEGIN
    UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
    where deleted is null or deletedDate is null
END

回答:


13

個々のステートメント(DML、DDLなど)は、それ自体がトランザクションです。つまり、ループの各反復の後(技術的には各ステートメントの後)、そのUPDATEステートメントの変更はすべて自動コミットされています。

もちろん例外はありますよね?経由で暗黙のトランザクションを有効にすることが可能であるSET IMPLICIT_TRANSACTIONS最初、その場合には、UPDATE文はあなたがしなければならないこと、トランザクションを開始するCOMMITか、ROLLBACK最後に。これは、ほとんどの場合デフォルトでオフになっているセッションレベルの設定です。

いつでもキャンセルできるように、明示的なBEGIN TRANSACTION / END TRANSACTIONステートメントを追加する必要がありますか?

いいえ。実際、プロセスを停止して再起動できるようにしたい場合、明示的なトランザクションを追加する(または暗黙的なトランザクションを有効にする)ことは悪い考えCOMMITです。その場合、手動でCOMMIT(SSMSを使用している場合)を発行する必要があります。または、SQLエージェントジョブからこれを実行している場合、その機会はなく、孤立したトランザクションが発生する可能性があります。


また、@CHUNK_SIZE小さい値に設定することもできます。ロックのエスカレーションは、通常、単一のオブジェクトで取得された5000ロックで発生します。行のサイズによっては、行ロックとページロックのどちらを実行しているかによって、その制限を超えている可能性があります。行のサイズが各ページに1行または2行しか収まらない場合、ページロックを実行している場合でも、常にこれにぶつかることがあります。

テーブルがパーティション化LOCK_ESCALATIONされAUTOている場合、エスカレーション時にテーブル全体ではなくパーティションのみをロックするようにテーブルのオプション(SQL Server 2008で導入)を設定するオプションがあります。または、どのテーブルでも同じオプションをに設定できますがDISABLE、その場合は十分に注意する必要があります。詳細については、ALTER TABLEを参照してください。

ロックのエスカレーションとしきい値について説明しているドキュメントは次のとおりです。ロックのエスカレーション(「SQL Server 2008 R2以降のバージョン」に適用されるとあります)。そして、ロックエスカレーションの検出と修正を扱ったブログ投稿があります:Microsoft SQL Serverでのロック(パート12 –ロックエスカレーション)


正確な質問とは無関係ですが、質問のクエリに関連して、ここで行うことができるいくつかの改善点があります(または少なくともそれを見るだけでそのように見えます)。

  1. ループのWHILE (@@ROWCOUNT = @CHUNK_SIZE)場合、最後の反復で更新された行の数がUPDATEに要求された量よりも少ない場合、実行する作業が残っていないため、実行する方が少し良いです。

  2. 場合はdeleted、フィールドはBIT、データ型、値はかどうかによって決まるということではないdeletedDateですか2000-01-01?なぜ両方が必要なのですか?

  3. これらの2つのフィールドが新しくNULL、オンライン/非ブロッキング操作である可能性があるためにそれらを追加し、それらを「デフォルト」値に更新したい場合、それは不要でした。SQL Server 2012(Enterprise Editionのみ)以降NOT NULLでは、DEFAULTの値が定数である限り、DEFAULT制約を持つ列を追加しても非ブロッキング操作になります。したがって、フィールドをまだ使用していない場合はNOT NULL、DEFAULT制約を指定して、ドロップして再追加するだけです。

  4. このUPDATEの実行中に他のプロセスがこれらのフィールドを更新していない場合は、更新するレコードをキューに入れて、そのキューで作業するほうが高速です。変更が必要なセットを取得するために毎回テーブルを再クエリする必要があるため、現在のメソッドではパフォーマンスが低下します。代わりに、次のようにして、これらの2つのフィールドでテーブルを1回だけスキャンし、対象を絞ったUPDATEステートメントのみを発行することができます。また、キューの初期設定では、更新されていないレコードが検出されるだけなので、いつでもプロセスを停止して後で開始してもペナルティはありません。

    1. クラスター化インデックスのキーフィールドのみが含まれる一時テーブル(#FullSet)を作成します。
    2. 同じ構造の2番目の一時テーブル(#CurrentSet)を作成します。
    3. 経由で#FullSetに挿入 SELECT TOP(n) KeyField1, KeyField2 FROM [huge-table] where deleted is null or deletedDate is null;

      TOP(n)そこでは、テーブルの大きさによるものです。テーブルに1億行あるため、特にプロセスを頻繁に停止して後で再起動する場合は、キーのセット全体をキューテーブルに入力する必要はありません。したがって、おそらくn100万に設定し、それを完了するまで実行します。これは、100万セット(またはそれ以下)を実行するSQLエージェントジョブでいつでもスケジュールでき、次にスケジュールされた時間を待って再びピックアップします。次に、20分ごとに実行するようにスケジュールすることができます。これによりn、のセットの間に強制呼吸の余地ができますが、プロセス全体が無人で終了します。次に、何もする必要がないときに、ジョブにそれ自体を削除させます:-)。

    4. ループでは、次のようにします。
      1. 次のようなものを介して現在のバッチを入力します DELETE TOP (4995) FROM #FullSet OUTPUT Deleted.KeyField INTO #CurrentSet (KeyField);
      2. IF (@@ROWCOUNT = 0) BREAK;
      3. 次のようなものを使用してUPDATEを実行します。 UPDATE ht SET ht.deleted = 0, ht.deletedDate='2000-01-01' FROM [huge-table] ht INNER JOIN #CurrentSet cs ON cs.KeyField = ht.KeyField;
      4. 現在のセットをクリアします。 TRUNCATE TABLE #CurrentSet;
  5. 場合によっては、フィルタされたインデックスを追加SELECTして、#FullSet一時テーブルにフィードされるを支援すると便利です。このようなインデックスの追加に関する考慮事項は次のとおりです。
    1. WHERE条件はクエリのWHERE条件と一致する必要があるため、 WHERE deleted is null or deletedDate is null
    2. プロセスの最初では、ほとんどの行がWHERE条件に一致するため、インデックスはそれほど役に立ちません。これを追加する前に、50%マークのあたりまで待つことをお勧めします。もちろん、いくら役立つか、いつインデックスを追加するのが最適かは、いくつかの要因により異なります。そのため、試行錯誤が少しあります。
    3. ベースデータは頻繁に変更されるため、統計を手動で更新したり、インデックスを再構築して最新の状態に維持したりする必要がある場合があります。
    4. インデックスは、を支援している間、その操作中に更新する必要がある別のオブジェクトであり、I / Oが増えるためSELECT、を傷つけることに注意してくださいUPDATE。これは、フィルターされたインデックス(フィルターに一致する行が少ないため、行を更新すると縮小します)を使用することと、インデックスを追加するのに少し待つこと(最初はあまり役に立たない場合、発生する理由がないこと)の両方に影響します。追加のI / O)。

更新:上記の提案の完全な実装については、この質問に関連する質問への私の回答を参照してください。ステータスを追跡し、完全にキャンセルするメカニズムが含まれます。SQLサーバー:小さなチャンクの巨大なテーブルのフィールドの更新:取得方法進行状況


#4での提案は、場合によってはより高速になる可能性がありますが、追加するコードはかなり複雑になるようです。単純な方法から始めることをお勧めします。それでも問題が解決しない場合は、代替案を検討してください。
ベーコンビット2017年

@BaconBitsシンプルなものから始めることに同意しました。公平を期すために、これらの提案はすべてのシナリオに適用されることを意図していませんでした。問題は、非常に大きな(1億行以上)テーブルを処理することです。
ソロモンルツキー2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.