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

时间:2017-01-24 18:18:20

标签: sql-server tsql

我对锁和提示相当新。

我有一张频繁SELECTINSERT操作的表格。该表有1100万条记录。

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

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

UPDATE 
    SomeTable WITH (ROWLOCK)
SET
    NewColumn = OldColumn

问题:

  1. NOLOCK代替ROWLOCK吗?请注意,一旦将记录插入表中,OldColumn的值就不会更改,因此NOLOCK不会导致脏读。
  2. 在这种情况下,NOLOCK是否有意义,因为SQL Server必须获得UPDATE的更新锁。
  3. 有没有更好的方法来实现这个目标?
  4. 我知道要避免使用提示,SQL Server通常会做出更明智的选择,但我不希望在此次更新期间将表锁定。

4 个答案:

答案 0 :(得分:4)

尝试批量更新。

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

答案 1 :(得分:2)

我采用了@ pacreely的方法(参见他对这个问题的回答)批量更新并创建了update...top变体。我添加了(rowlock)提示告诉SQL服务器将锁保持在行级别。

有关详细信息,请参阅update...top。另请注意,在order bytopupdateinsert语句中使用merge时,您无法使用delete,因此引用的行不会按任何顺序排列。

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

答案 2 :(得分:1)

答案 3 :(得分:0)

最近,我们有一个案例,我们想做类似的事情,但是要花几天时间(每次运行只能更新一定数量的记录,并且只能在特定时间内更新)。最近的数据很好,但是需要更新数百万行的旧数据。我们的数据表如下所示:

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