Sql server 更新大量记录时如何避免UPDATE语句锁定整个表
我对锁和提示相当陌生 我有一个非常频繁的SELECT和INSERT操作的表。该表有1100万条记录 我向其中添加了一个新列,需要将同一表中现有列的数据复制到新列 我计划使用ROWLOCK提示来避免将锁升级到表级锁,并阻止表上的所有其他操作。例如:Sql server 更新大量记录时如何避免UPDATE语句锁定整个表,sql-server,tsql,Sql Server,Tsql,我对锁和提示相当陌生 我有一个非常频繁的SELECT和INSERT操作的表。该表有1100万条记录 我向其中添加了一个新列,需要将同一表中现有列的数据复制到新列 我计划使用ROWLOCK提示来避免将锁升级到表级锁,并阻止表上的所有其他操作。例如: UPDATE SomeTable WITH (ROWLOCK) SET NewColumn = OldColumn 问题: 会用NOLOCK代替ROWLOCK吗?注意,一旦将记录插入表中,OldColumn的值就不会更改,因此NOL
UPDATE
SomeTable WITH (ROWLOCK)
SET
NewColumn = OldColumn
问题:
会用NOLOCK代替ROWLOCK吗?注意,一旦将记录插入表中,OldColumn的值就不会更改,因此NOLOCK不会导致脏读。
在这种情况下,NOLOCK是否有意义,因为SQL Server无论如何都必须为更新获取更新锁。
有没有更好的方法来实现这一点?
我知道要避免提示,SQL Server通常会做出更明智的选择,但我不想在更新期间将表锁定。此问题在StackExchange的数据库管理员站点上有答案:尝试批量更新
DECLARE @Batch INT = 1000
DECLARE @Rowcount INT = @Batch
WHILE @Rowcount > 0
BEGIN
;WITH CTE AS
(
SELECT TOP (@Batch) NewColumn,OldColumn
FROM SomeTable
WHERE NewColumn <> OldColumn
OR (NewColumn IS NULL AND OldColumn IS NOT NULL)
)
UPDATE cte
SET NewColumn = OldColumn;
SET @Rowcount = @@ROWCOUNT
END
我采用@pacreely的方法查看他关于批量更新问题的答案,并创建了一个更新…顶部变体。我添加了rowlock提示,告诉SQLServer将锁保持在行级别 有关详细信息,请参阅。还要注意,在update、insert、merge、delete语句中使用top时,不能使用order by,因此引用的行不会按任何顺序排列
declare @BatchSize int = 1000
declare @RowCount int = @BatchSize
while @RowCount > 0
begin
update top (@BatchSize) SomeTable with (rowlock)
set NewColumn = OldColumn
where
NewColumn <> OldColumn or
(
NewColumn is null and
OldColumn is not null
)
select @RowCount = @@rowcount
end
我们最近遇到了一个案例,我们想做一些类似的事情,但是在几天内,每次运行只更新一定数量的记录,并且只在特定的时间内更新。最新的数据很好,但数百万行旧数据需要更新。我们的数据表如下所示:
Create Table FileContent
(
FileContent varchar(max),
File_PK bigint,
NewFileContent varchar(max)
)
我们只需要更新某些行,但需要更新数百万行。我们创建了一个表来存储我们的进度,这样我们就可以使用计划作业来迭代和更新主表,然后用需要更新的主表记录的主键填充该表:
Create Table FilesToUpdate
(
File_PK bigint,
IsUpdated bit NOT NULL DEFAULT 0
)
然后,我们计划使用以下脚本进行更新,以供您自己使用,并根据系统的工作情况调整批量大小和计划
/***
Script to update and fix records.
***/
DECLARE @Rowcount INT = 1 --
, @BatchSize INT = 100 -- how many rows will be updated on each iteration of the loop
, @BatchesToRun INT = 25 -- the max number of times the loop will iterate
, @StartingRecord BIGINT = 1;
-- Get the highest File_PK not already fixed as a starting point.
Select @StartingRecord = MAX(File_PK) From FilesToUpdate where IsUpdated = 0
-- While there are still rows to update and we haven't hit our limit on iterations...
WHILE (@Rowcount > 0 and @BatchesToRun > 0)
BEGIN
print Concat('StartingRecord (Start of Loop): ', @StartingRecord)
UPDATE FileContent SET NewFileContent = 'New value here'
WHERE File_PK BETWEEN (@StartingRecord - @BatchSize + 1) AND @StartingRecord;
-- @@Rowcount is the number of records affected by the last statement. If this returns 0, the loop will stop because we've run out of things to update.
SET @Rowcount = @@ROWCOUNT;
print Concat('RowCount: ', @Rowcount)
-- Record which PKs were updated so we know where to start next time around.
UPDATE FilesToUpdate Set IsUpdated = 1 where File_PK BETWEEN (@StartingRecord - @BatchSize + 1) AND @StartingRecord;
-- The loop will stop after @BatchSize*@BatchesToRun records are updated.
-- If there aren't that many records left to update, the @Rowcount checks will stop it.
SELECT @BatchesToRun = @BatchesToRun - 1
print Concat('Batches Remaining: ',@BatchesToRun)
-- Set the starting record for the next time through the loop.
SELECT @StartingRecord -= @BatchSize
print Concat('StartingRecord (End of Loop): ', @StartingRecord)
END
您拥有的语句将在单个事务中更新表中的所有记录,因此无论您如何剪切它,都将锁定事务中的所有记录。我曾经看到Kendra Little在更新方面做了一个很好的技巧。更新不会并行进行,但为了加快速度,她在cte中使用select来实现并行读取。感谢您显示cte的更新级联到基础表。在我的回答中,使用update…top组合的批更新还有一个变体。保持批大小小于5000。这不只是更新cte而不是实际的SomeTable吗?@Basil cte不是对象,它是对象SomeTable的表示形式。如果您查看使用CTE的查询的执行计划,就优化人员而言,CTE不存在。