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
個々のステートメント(DML、DDLなど)は、それ自体がトランザクションです。つまり、ループの各反復の後(技術的には各ステートメントの後)、UPDATE
ステートメントが変更したものはすべて自動コミットされます。
もちろん例外はありますよね? SET IMPLICIT_TRANSACTIONS を介して暗黙のトランザクションを有効にすることができます。その場合、最初のUPDATE
ステートメントがトランザクションを開始し、最後にCOMMIT
またはROLLBACK
を実行する必要があります。これは、ほとんどの場合デフォルトでオフになっているセッションレベルの設定です。
いつでもキャンセルできるように、明示的なBEGIN TRANSACTION/END TRANSACTIONステートメントを追加する必要がありますか?
いいえ。実際、プロセスを停止して再起動できるようにしたい場合、プロセスを停止するとCOMMIT
を実行する前にプロセスをキャッチできるため、明示的なトランザクションを追加する(または暗黙的なトランザクションを有効にする)ことはお勧めできません。その場合、COMMIT
を手動で発行する必要があります(SSMSを使用している場合)、またはこれをSQLエージェントジョブから実行している場合は、その機会がなく、孤立したトランザクションが発生する可能性があります。
また、_@CHUNK_SIZE
_を小さい数値に設定することもできます。ロックのエスカレーションは通常、単一のオブジェクトで取得された5000個のロックで発生します。行のサイズに応じて、行ロックとページロックを実行している場合は、その制限を超えている可能性があります。行のサイズが各ページに1行または2行しか収まらない場合、ページロックを実行している場合でも、常にこれにぶつかることがあります。
テーブルがパーティション化されている場合、エスカレーション時にテーブル全体ではなくパーティションのみをロックするように、テーブルの_LOCK_ESCALATION
_オプション(SQL Server 2008で導入)をAUTO
に設定するオプションがあります。または、どのテーブルでも同じオプションを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のみ)以降、DEFAULT制約を持つ_NOT NULL
_列の追加は、DEFAULTの値が定数である限り、非ブロッキング操作です。そのため、まだフィールドを使用していない場合は、ドロップして_NOT NULL
_としてドロップし、DEFAULT制約を追加します。
このUPDATEの実行中に他のプロセスがこれらのフィールドを更新していない場合は、更新するレコードをキューに入れて、そのキューで作業するほうが高速です。変更が必要なセットを取得するために毎回テーブルを再クエリする必要があるため、現在のメソッドではパフォーマンスが低下します。代わりに、次のようにして、これらの2つのフィールドでテーブルを1回だけスキャンしてから、対象を絞ったUPDATEステートメントのみを発行することができます。また、キューの初期設定では、更新されていないレコードが見つかるだけなので、いつでもプロセスを停止して後で開始してもペナルティはありません。
SELECT TOP(n) KeyField1, KeyField2 FROM [huge-table] where deleted is null or deletedDate is null;
を使用して#FullSetに挿入します
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 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;
_#FullSet
_一時テーブルにフィードするSELECT
を支援することが役立ちます。このようなインデックスの追加に関する考慮事項は次のとおりです。WHERE deleted is null or deletedDate is null
_SELECT
を支援している間、UPDATE
に悪影響を与えることに注意してください。これは、その操作中に更新する必要がある別のオブジェクトであり、I/Oが増えるためです。これは、フィルターされたインデックス(フィルターに一致する行が少ないため、行を更新すると縮小する)を使用することと、インデックスを追加するのにしばらく待つこと(最初は非常に役に立たない場合、発生する理由がないこと)の両方に関係します。追加のI/O)。UPDATE:ステータスを追跡するメカニズムを含む、上記の提案の完全な実装については、この質問に関連する質問への私の回答を参照してください。きれいにキャンセル: SQLサーバー:巨大なテーブルのフィールドを小さなチャンクで更新:進行状況/ステータスを取得する方法