Sql server 更新大量记录时如何避免UPDATE语句锁定整个表

Sql server 更新大量记录时如何避免UPDATE语句锁定整个表,sql-server,tsql,Sql Server,Tsql,我对锁和提示相当陌生 我有一个非常频繁的SELECT和INSERT操作的表。该表有1100万条记录 我向其中添加了一个新列,需要将同一表中现有列的数据复制到新列 我计划使用ROWLOCK提示来避免将锁升级到表级锁,并阻止表上的所有其他操作。例如: UPDATE SomeTable WITH (ROWLOCK) SET NewColumn = OldColumn 问题: 会用NOLOCK代替ROWLOCK吗?注意,一旦将记录插入表中,OldColumn的值就不会更改,因此NOL

我对锁和提示相当陌生

我有一个非常频繁的SELECT和INSERT操作的表。该表有1100万条记录

我向其中添加了一个新列,需要将同一表中现有列的数据复制到新列

我计划使用ROWLOCK提示来避免将锁升级到表级锁,并阻止表上的所有其他操作。例如:

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不存在。