当我回答相关问题(这个while循环中是否需要显式事务吗?)时,我没有意识到这个问题,但是为了完整起见,我将在此处解决此问题,因为这不是我在该链接答案中的建议的一部分。
由于我建议通过SQL Agent作业(毕竟是1亿行)安排此时间,因此我认为发送状态消息到客户端(即SSMS)的任何形式都不理想(尽管如果需要其他项目,那么我同意弗拉基米尔(Vladimir)的观点:使用RAISERROR('', 10, 1) WITH NOWAIT;
是必经之路。
在这种特殊情况下,我将创建一个状态表,该状态表可以针对每个循环使用到目前为止已更新的行数进行更新。投入当前时间对流程进行心跳动动也无济于事。
鉴于您希望能够取消并重新启动该过程, 我厌倦了在显式事务中将主表的UPDATE与状态表的UPDATE包装在一起。但是,如果您由于取消状态表而感到状态表不同步,则只需用手动更新即可轻松地使用当前值刷新状态表COUNT(*) FROM [huge-table] WHERE deleted IS NOT NULL AND deletedDate IS NOT NULL
。并且有两个要更新的表(即主表和状态表),我们应该使用显式事务来使这两个表保持同步,但是如果您在某个时间取消了该进程,我们不希望冒孤立事务的风险。开始事务但尚未提交之后的时间点。只要您不停止SQL Agent作业,这样做就应该是安全的。
您如何在不停止的情况下停止该进程?通过要求它停止:-)。是的 通过向进程发送一个“信号”(类似于kill -3
Unix),您可以请求该进程在下一个方便的时刻停止(即,当没有活动事务时!),并让其自行清理所有干净整洁的东西。
如何在另一个会话中与正在运行的进程进行通信?通过使用与我们创建的机制相同的机制将其当前状态传达给您:状态表。我们只需要添加一列,该过程将在每个循环的开始进行检查,以便知道是继续还是中止。并且由于目的是将其安排为SQL Agent作业(每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 Agent作业中运行还是在其他人的计算机上的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
有时,它可以帮助优化器知道最大行将影响多少行,这样就不会浪费时间寻找更多行。
将PRIMARY KEY添加到#CurrentSet
临时表。这里的想法是通过JOIN到1亿行表来帮助优化器。
只是为了避免模棱两可,没有任何理由将PK添加到#FullSet
临时表,因为它只是顺序不相关的简单队列表。
- 在某些情况下,添加过滤索引有助于将索引
SELECT
馈入#FullSet
临时表。以下是与添加此类索引有关的一些注意事项:
- WHERE条件应与查询的WHERE条件匹配,因此
WHERE deleted is null or deletedDate is null
- 在该过程的开始,大多数行都将符合您的WHERE条件,因此索引并不是那么有用。您可能要等到50%左右的某个位置才能添加。当然,由于几个因素的不同,它的帮助程度和最佳添加索引的时间也有所不同,因此这是反复试验的结果。
- 您可能必须手动更新STATS和/或重建索引以使其保持最新,因为基础数据的更改非常频繁
- 请务必记住,索引在帮助的同时
SELECT
会伤害,UPDATE
因为索引是该操作期间必须更新的另一个对象,因此会有更多的I / O。这既可以使用“过滤索引”(由于匹配过滤器的行越少,更新的索引就越小),又需要等待一会儿才能添加索引(如果一开始它并没有多大帮助,那么就没有理由额外的I / O)。