個々のステートメント(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 –ロックエスカレーション)。
正確な質問とは無関係ですが、質問のクエリに関連して、ここで行うことができるいくつかの改善点があります(または少なくともそれを見るだけでそのように見えます)。
ループのWHILE (@@ROWCOUNT = @CHUNK_SIZE)
場合、最後の反復で更新された行の数がUPDATEに要求された量よりも少ない場合、実行する作業が残っていないため、実行する方が少し良いです。
場合はdeleted
、フィールドはBIT
、データ型、値はかどうかによって決まるということではないdeletedDate
ですか2000-01-01
?なぜ両方が必要なのですか?
これらの2つのフィールドが新しくNULL
、オンライン/非ブロッキング操作である可能性があるためにそれらを追加し、それらを「デフォルト」値に更新したい場合、それは不要でした。SQL Server 2012(Enterprise Editionのみ)以降NOT NULL
では、DEFAULTの値が定数である限り、DEFAULT制約を持つ列を追加しても非ブロッキング操作になります。したがって、フィールドをまだ使用していない場合はNOT NULL
、DEFAULT制約を指定して、ドロップして再追加するだけです。
このUPDATEの実行中に他のプロセスがこれらのフィールドを更新していない場合は、更新するレコードをキューに入れて、そのキューで作業するほうが高速です。変更が必要なセットを取得するために毎回テーブルを再クエリする必要があるため、現在のメソッドではパフォーマンスが低下します。代わりに、次のようにして、これらの2つのフィールドでテーブルを1回だけスキャンし、対象を絞ったUPDATEステートメントのみを発行することができます。また、キューの初期設定では、更新されていないレコードが検出されるだけなので、いつでもプロセスを停止して後で開始してもペナルティはありません。
- クラスター化インデックスのキーフィールドのみが含まれる一時テーブル(#FullSet)を作成します。
- 同じ構造の2番目の一時テーブル(#CurrentSet)を作成します。
経由で#FullSetに挿入 SELECT TOP(n) KeyField1, KeyField2 FROM [huge-table] where deleted is null or deletedDate is null;
TOP(n)
そこでは、テーブルの大きさによるものです。テーブルに1億行あるため、特にプロセスを頻繁に停止して後で再起動する場合は、キーのセット全体をキューテーブルに入力する必要はありません。したがって、おそらくn
100万に設定し、それを完了するまで実行します。これは、100万セット(またはそれ以下)を実行するSQLエージェントジョブでいつでもスケジュールでき、次にスケジュールされた時間を待って再びピックアップします。次に、20分ごとに実行するようにスケジュールすることができます。これによりn
、のセットの間に強制呼吸の余地ができますが、プロセス全体が無人で終了します。次に、何もする必要がないときに、ジョブにそれ自体を削除させます:-)。
- ループでは、次のようにします。
- 次のようなものを介して現在のバッチを入力します
DELETE TOP (4995) FROM #FullSet OUTPUT Deleted.KeyField INTO #CurrentSet (KeyField);
IF (@@ROWCOUNT = 0) BREAK;
- 次のようなものを使用して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;
- 現在のセットをクリアします。
TRUNCATE TABLE #CurrentSet;
- 場合によっては、フィルタされたインデックスを追加
SELECT
して、#FullSet
一時テーブルにフィードされるを支援すると便利です。このようなインデックスの追加に関する考慮事項は次のとおりです。
- WHERE条件はクエリのWHERE条件と一致する必要があるため、
WHERE deleted is null or deletedDate is null
- プロセスの最初では、ほとんどの行がWHERE条件に一致するため、インデックスはそれほど役に立ちません。これを追加する前に、50%マークのあたりまで待つことをお勧めします。もちろん、いくら役立つか、いつインデックスを追加するのが最適かは、いくつかの要因により異なります。そのため、試行錯誤が少しあります。
- ベースデータは頻繁に変更されるため、統計を手動で更新したり、インデックスを再構築して最新の状態に維持したりする必要がある場合があります。
- インデックスは、を支援している間、その操作中に更新する必要がある別のオブジェクトであり、I / Oが増えるため
SELECT
、を傷つけることに注意してくださいUPDATE
。これは、フィルターされたインデックス(フィルターに一致する行が少ないため、行を更新すると縮小します)を使用することと、インデックスを追加するのに少し待つこと(最初はあまり役に立たない場合、発生する理由がないこと)の両方に影響します。追加のI / O)。
更新:上記の提案の完全な実装については、この質問に関連する質問への私の回答を参照してください。ステータスを追跡し、完全にキャンセルするメカニズムが含まれます。SQLサーバー:小さなチャンクの巨大なテーブルのフィールドの更新:取得方法進行状況