関連する質問(このwhileループでは明示的なトランザクションが必要ですか?)に答えたとき、私はこの質問に気づいていませんでしたが、完全を期すために、リンクされた回答の私の提案の一部ではなかったので、ここでこの問題に対処します。
私はこれをSQLエージェントジョブ(結局1億行)を介してスケジュールすることを提案しているので、クライアント(つまりSSMS)にステータスメッセージを送信する方法は理想的ではないと思います(ただし、他のプロジェクトが必要になったときRAISERROR('', 10, 1) WITH NOWAIT;
は、ウラジミールに同意します。
この特定のケースでは、これまでに更新された行数でループごとに更新できるステータステーブルを作成します。そして、プロセスにハートビートを与えるために現在の時間を投入することは害にはなりません。
プロセスをキャンセルして再開できるようにしたい場合、 メインテーブルのUPDATEとステータステーブルのUPDATEを明示的なトランザクションでラップすることにうんざりしています。ただし、キャンセルのためにステータステーブルが同期されていない場合は、を使用して手動で更新するだけで、現在の値で簡単に更新できCOUNT(*) FROM [huge-table] WHERE deleted IS NOT NULL AND deletedDate IS NOT NULL
ます。UPDATEする2つのテーブル(メインテーブルとステータステーブル)がある場合、明示的なトランザクションを使用してこれらの2つのテーブルの同期を維持する必要がありますが、プロセスをキャンセルした場合に孤立したトランザクションが発生するリスクはありません。トランザクションを開始したがコミットしていない後の時点。これは、SQLエージェントジョブを停止しない限り安全です。
ええと、それを停止せずにプロセスを停止するにはどうすればよいでしょうか。:-)を停止するように要求します。うん。プロセスに「シグナル」(kill -3
Unix と同様)を送信することにより、次の都合の良い瞬間(つまり、アクティブなトランザクションがないとき)に停止するように要求し、すべてをきれいに整頓することができます。
別のセッションで実行中のプロセスとどのように通信できますか?そのために作成したメカニズムと同じメカニズムを使用して、現在のステータスを通知します。ステータステーブルです。処理を続行するか中止するかがわかるように、各ループの開始時にプロセスがチェックする列を追加するだけです。また、これは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;
更新:
この操作のパフォーマンスを向上させるために、次のことを試してみてください。役立つことは保証されていませんが、おそらくテストする価値があります。1億行を更新するため、いくつかのバリエーションをテストする時間と機会がたくさんあります;-)。
TOP (@UpdateRows)
UPDATEクエリに追加して、一番上の行が次のように
UPDATE TOP (@UpdateRows) ht
なるようにします。最大数が影響を受ける行数をオプティマイザが知るのに役立つことがあり、それ以上の検索に時間を浪費しません。
#CurrentSet
一時テーブルに主キーを追加します。ここでのアイデアは、オプティマイザが1億行のテーブルにJOINするのを助けることです。
また、あいまいにならないように説明するために、#FullSet
一時テーブルにPKを追加する理由はありません。これは、順序が関係しない単純なキューテーブルであるためです。
- 場合によっては、フィルタされたインデックスを追加
SELECT
して、#FullSet
一時テーブルにフィードされるを支援すると便利です。このようなインデックスの追加に関する考慮事項は次のとおりです。
- WHERE条件はクエリのWHERE条件と一致する必要があるため、
WHERE deleted is null or deletedDate is null
- プロセスの最初では、ほとんどの行がWHERE条件に一致するため、インデックスはそれほど役に立ちません。これを追加する前に、50%マークのあたりまで待つことをお勧めします。もちろん、いくら役立つか、いつインデックスを追加するのが最適かは、いくつかの要因により異なります。そのため、試行錯誤が少しあります。
- ベースデータは頻繁に変更されるため、統計を手動で更新したり、インデックスを再構築して最新の状態に維持したりする必要がある場合があります。
- インデックスは、を支援している間、その操作中に更新する必要がある別のオブジェクトであり、I / Oが増えるため
SELECT
、を傷つけることに注意してくださいUPDATE
。これは、フィルターされたインデックス(フィルターに一致する行が少ないため、行を更新すると縮小します)を使用することと、インデックスを追加するのに少し待つこと(最初はあまり役に立たない場合、発生する理由がないこと)の両方に影響します。追加のI / O)。