我们有一个非常大(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
Run Code Online (Sandbox Code Playgroud)
Sol*_*zky 12
我在回答相关问题时没有意识到这个问题(在这个 while 循环中是否需要显式交易?),但为了完整起见,我将在这里解决这个问题,因为它不是我在链接答案中的建议的一部分.
由于我建议通过 SQL Agent 作业(毕竟它是 1 亿行)来安排它,因此我认为向客户端(即 SSMS)发送状态消息的任何形式都不是理想的(尽管如果是如果需要其他项目,那么我同意 Vladimir 的观点,即使用RAISERROR('', 10, 1) WITH NOWAIT;是可行的方法)。
在这种特殊情况下,我将创建一个状态表,可以在每个循环中使用迄今为止更新的行数进行更新。投入当前时间来关注这个过程并没有什么坏处。
鉴于您希望能够取消并重新启动该过程, 我厌倦了将主表的 UPDATE 与状态表的 UPDATE 包装在显式事务中。但是,如果您觉得由于取消而导致状态表不同步,只需使用并且有两个表要 UPDATE(即主表和状态表),我们应该使用显式事务来保持这两个表同步,但是如果您在某个时间取消进程,我们不想冒孤立事务的风险在它开始事务但尚未提交之后的点。只要您不停止 SQL 代理作业,这应该是安全的。COUNT(*) FROM [huge-table] WHERE deleted IS NOT NULL AND deletedDate IS NOT NULL.
你怎么能在没有,嗯,好吧,停止它的情况下停止这个过程?通过要求它停止:-)。是的。通过向进程发送一个“信号”(类似于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;
Run Code Online (Sandbox Code Playgroud)
然后,您可以随时使用以下查询检查状态:
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;
Run Code Online (Sandbox Code Playgroud)
想要暂停进程,无论它是在 SQL 代理作业中运行,还是在其他人计算机上的 SSMS 中运行?赶紧跑:
UPDATE ht
SET ht.PauseProcess = 1
FROM dbo.HugeTable_TempStatus ts;
Run Code Online (Sandbox Code Playgroud)
希望该过程能够再次开始备份?赶紧跑:
UPDATE ht
SET ht.PauseProcess = 0
FROM dbo.HugeTable_TempStatus ts;
Run Code Online (Sandbox Code Playgroud)
更新:
这里有一些额外的尝试,可能会提高此操作的性能。没有一个保证有帮助,但可能值得测试。并且有 1 亿行要更新,您有足够的时间/机会来测试一些变化;-)。
TOP (@UpdateRows)到 UPDATE 查询,使第一行看起来像:UPDATE TOP (@UpdateRows) ht将 PRIMARY KEY 添加到#CurrentSet临时表。这里的想法是帮助优化器将 JOIN 连接到 1 亿行表。
并且只是为了避免歧义,没有任何理由将 PK 添加到#FullSet临时表,因为它只是一个简单的队列表,其中的顺序无关紧要。
SELECT输入#FullSet临时表的索引。以下是与添加此类索引相关的一些注意事项:
WHERE deleted is null or deletedDate is nullSELECT会伤害 ,UPDATE因为它是另一个必须在该操作期间更新的对象,因此需要更多的 I/O。这既可以使用过滤索引(由于与过滤器匹配的行较少,因此在更新行时会缩小),并等待一段时间添加索引(如果它在开始时不会非常有用,那么没有理由招致额外的 I/O)。| 归档时间: |
|
| 查看次数: |
9827 次 |
| 最近记录: |