非常に大きな(1億行)テーブルがあり、その上のいくつかのフィールドを更新する必要があります。
ログシッピングなどの場合も、一口サイズのトランザクションを維持する必要があります。
コードは次のとおりです。
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
関連する質問( このwhileループでは明示的なトランザクションが必要ですか? )に答えたとき、この質問に気づいていませんでしたが、完全を期すために、この問題は一部ではなかったので、ここで取り上げますそのリンクされた答えで私の提案の。
私はこれをSQLエージェントジョブ(結局1億行)を介してスケジュールすることを提案しているので、ステータスメッセージをクライアント(つまりSSMS)に送信する形式は理想的ではないと思います(ただし、他のプロジェクトが必要になったときは、RAISERROR('', 10, 1) WITH NOWAIT;
を使用するのがよいとVladimirに同意します).
この特定のケースでは、これまでに更新された行数でループごとに更新できるステータステーブルを作成します。また、プロセスにハートビートを設定するために現在の時刻を投入しても問題はありません。
プロセスをキャンセルして再開できるようにしたい場合、 メインテーブルのUPDATEとステータステーブルのUPDATEを明示的なトランザクションでラップすることにうんざりしています。ただし、キャンセルが原因でステータステーブルが同期されていない場合は、 UPDATEする2つのテーブル(メインテーブルとステータステーブル)がある場合すべき明示的なトランザクションを使用して、これら2つのテーブルの同期を維持しますが、孤立したトランザクションが発生する危険を冒したくありません。トランザクションを開始した後でコミットしていない時点でプロセスをキャンセルした場合。これは、SQLエージェントジョブを停止しない限り安全です。COUNT(*) FROM [huge-table] WHERE deleted IS NOT NULL AND deletedDate IS NOT NULL
を使用して手動で更新するだけで、現在の値で簡単に更新できます。
ええと、それを停止せずにプロセスを停止するにはどうすればよいでしょうか。 :-)を停止するように要求します。うん。プロセスに「シグナル」(Unixの_kill -3
_と同様)を送信することにより、次の都合の良い瞬間(つまり、アクティブなトランザクションがない場合)で停止するように要求し、すべてを正常にクリーンアップすることができます。きちんとした。
別のセッションで実行中のプロセスとどのように通信できますか?そのために作成したメカニズムと同じメカニズムを使用して、現在のステータスを通知します。ステータステーブルです。処理を続行するか中止するかがわかるように、各ループの開始時にプロセスがチェックする列を追加するだけです。また、これはSQLエージェントジョブとしてスケジュールすることを目的としているため(10分または20分ごとに実行)、プロセスがちょうど進行している場合は、一時テーブルに100万行を入力しても意味がないため、最初から確認する必要もあります。しばらくして終了し、そのデータを使用しません。
_DECLARE @BatchRows INT = 1000000,
@UpdateRows INT = 4995;
IF (OBJECT_ID(N'dbo.HugeTable_TempStatus') IS NULL)
BEGIN
CREATE TABLE dbo.HugeTable_TempStatus
(
RowsUpdated INT NOT NULL, -- updated by the process
LastUpdatedOn DATETIME NOT NULL, -- updated by the process
PauseProcess BIT NOT NULL -- read by the process
);
INSERT INTO dbo.HugeTable_TempStatus (RowsUpdated, LastUpdatedOn, PauseProcess)
VALUES (0, GETDATE(), 0);
END;
-- First check to see if we should run. If no, don't waste time filling temp table
IF (EXISTS(SELECT * FROM dbo.HugeTable_TempStatus WHERE PauseProcess = 1))
BEGIN
PRINT 'Process is paused. No need to start.';
RETURN;
END;
CREATE TABLE #FullSet (KeyField1 DataType1, KeyField2 DataType2);
CREATE TABLE #CurrentSet (KeyField1 DataType1, KeyField2 DataType2);
INSERT INTO #FullSet (KeyField1, KeyField2)
SELECT TOP (@BatchRows) ht.KeyField1, ht.KeyField2
FROM dbo.HugeTable ht
WHERE ht.deleted IS NULL
OR ht.deletedDate IS NULL
WHILE (1 = 1)
BEGIN
-- Check if process is paused. If yes, just exit cleanly.
IF (EXISTS(SELECT * FROM dbo.HugeTable_TempStatus WHERE PauseProcess = 1))
BEGIN
PRINT 'Process is paused. Exiting.';
BREAK;
END;
-- grab a set of rows to update
DELETE TOP (@UpdateRows)
FROM #FullSet
OUTPUT Deleted.KeyField1, Deleted.KeyField2
INTO #CurrentSet (KeyField1, KeyField2);
IF (@@ROWCOUNT = 0)
BEGIN
RAISERROR(N'All rows have been updated!!', 16, 1);
BREAK;
END;
BEGIN TRY
BEGIN TRAN;
-- do the update of the main table
UPDATE ht
SET ht.deleted = 0,
ht.deletedDate = '2000-01-01'
FROM dbo.HugeTable ht
INNER JOIN #CurrentSet cs
ON cs.KeyField1 = ht.KeyField1
AND cs.KeyField2 = ht.KeyField2;
-- update the current status
UPDATE ts
SET ts.RowsUpdated += @@ROWCOUNT,
ts.LastUpdatedOn = GETDATE()
FROM dbo.HugeTable_TempStatus ts;
COMMIT TRAN;
END TRY
BEGIN CATCH
IF (@@TRANCOUNT > 0)
BEGIN
ROLLBACK TRAN;
END;
THROW; -- raise the error and terminate the process
END CATCH;
-- clear out rows to update for next iteration
TRUNCATE TABLE #CurrentSet;
WAITFOR DELAY '00:00:01'; -- 1 second delay for some breathing room
END;
-- clean up temp tables when testing
-- DROP TABLE #FullSet;
-- DROP TABLE #CurrentSet;
_
その後、次のクエリを使用していつでもステータスを確認できます。
_SELECT sp.[rows] AS [TotalRowsInTable],
ts.RowsUpdated,
(sp.[rows] - ts.RowsUpdated) AS [RowsRemaining],
ts.LastUpdatedOn
FROM sys.partitions sp
CROSS JOIN dbo.HugeTable_TempStatus ts
WHERE sp.[object_id] = OBJECT_ID(N'ResizeTest')
AND sp.[index_id] < 2;
_
SQLエージェントジョブで実行されている場合でも、他のユーザーのコンピューターのSSMSで実行されている場合でも、プロセスを一時停止したいですか?とにかく走れ:
_UPDATE ht
SET ht.PauseProcess = 1
FROM dbo.HugeTable_TempStatus ts;
_
プロセスが再びバックアップを開始できるようにしたいですか?とにかく走れ:
_UPDATE ht
SET ht.PauseProcess = 0
FROM dbo.HugeTable_TempStatus ts;
_
UPDATE:
この操作のパフォーマンスを向上させる可能性があるいくつかの追加事項を次に示します。役立つことは保証されていませんが、おそらくテストする価値があります。 1億行を更新するため、いくつかのバリエーションをテストする時間と機会がたくさんあります;-)。
TOP (@UpdateRows)
をUPDATEクエリに追加して、上の行が次のようになるようにします。UPDATE TOP (@UpdateRows) ht
PRIMARY KEYを_#CurrentSet
_一時テーブルに追加します。ここでのアイデアは、オプティマイザが1億行のテーブルにJOINするのを助けることです。
また、あいまいにならないように説明するために、PKを_#FullSet
_一時テーブルに追加する理由はありません。これは、順序が関係しない単純なキューテーブルであるためです。
#FullSet
_一時テーブルにフィードするSELECT
を支援することが役立ちます。このようなインデックスの追加に関する考慮事項は次のとおりです。WHERE deleted is null or deletedDate is null
_SELECT
を支援している間、UPDATE
を傷つけることに注意してください。これは、その操作中に更新する必要がある別のオブジェクトであり、I/Oが増えるためです。これは、フィルターされたインデックス(フィルターに一致する行が少ないため、行を更新すると縮小する)を使用することと、インデックスを追加するのにしばらく待つこと(最初は非常に役に立たない場合、発生する理由がないこと)の両方に関係します。追加のI/O)。2番目の部分への回答:ループ中に出力を出力する方法。
Sys管理者が時々実行しなければならないいくつかの長期にわたるメンテナンス手順があります。
私はそれらをSSMSから実行し、PRINT
ステートメントが手順全体が終了した後にのみSSMSに表示されることにも気付きました。
したがって、私は RAISERROR
を重大度の低い値で使用しています:
DECLARE @VarTemp nvarchar(32);
SET @VarTemp = CONVERT(nvarchar(32), GETDATE(), 121);
RAISERROR (N'Your message. Current time is %s.', 0, 1, @VarTemp) WITH NOWAIT;
SQL Server 2008 StandardおよびSSMS 2012(11.0.3128.0)を使用しています。 SSMSで実行するための完全に機能する例を次に示します。
DECLARE @VarCount int = 0;
DECLARE @VarTemp nvarchar(32);
WHILE @VarCount < 3
BEGIN
SET @VarTemp = CONVERT(nvarchar(32), GETDATE(), 121);
--RAISERROR (N'Your message. Current time is %s.', 0, 1, @VarTemp) WITH NOWAIT;
--PRINT @VarTemp;
WAITFOR DELAY '00:00:02';
SET @VarCount = @VarCount + 1;
END
RAISERROR
をコメントアウトしてPRINT
のみを残すと、SSMSの[メッセージ]タブのメッセージは、バッチ全体が終了した後、6秒後に表示されます。
PRINT
をコメントアウトしてRAISERROR
を使用すると、SSMSの[メッセージ]タブのメッセージが6秒待たずに表示されますが、ループが進行します。
興味深いことに、RAISERROR
とPRINT
の両方を使用すると、両方のメッセージが表示されます。最初に最初のRAISERROR
からメッセージが送信され、次に2秒間遅延し、次に最初のPRINT
と2番目のRAISERROR
のように続きます。
他の場合では、別の専用のlog
テーブルを使用して、長期実行プロセスの現在の状態とタイムスタンプを説明する情報を含む行をテーブルに挿入するだけです。
長いプロセスが実行されている間、私は定期的にSELECT
テーブルからlog
を実行して、何が起こっているのかを確認します。
これには明らかに一定のオーバーヘッドがありますが、後で自分のペースで調べることができるログ(またはログの履歴)が残ります。
次のような別の接続からそれを監視できます:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT COUNT(*) FROM [huge-table] WHERE deleted IS NULL OR deletedDate IS NULL
やることがどれだけ残っているかを確認します。これは、SSMSなどで手動で実行するのではなく、アプリケーションがプロセスを呼び出しており、進行状況を表示する必要がある場合に役立ちます。 "非同期呼び出し(またはスレッド)が完了するまで時々チェックします。
分離レベルを可能な限り緩やかに設定することは、ロックの問題が原因でメイントランザクションに遅れることなく、妥当な時間内に戻る必要があることを意味します。もちろん、戻り値が少し不正確であることを意味する可能性がありますが、単純な進行状況メーターとして、これはまったく問題になりません。