如何避免表锁升级?

Ale*_*sko 6 sql-server extended-events lock-escalation sql-server-2017 batch-processing

我有一项任务要更新生产表中的 500 万行,而无需长时间锁定整个表

所以,我使用了多次帮助我的方法 - 一次更新前 (N) 行,块之间的间隔为 1-N 秒

这次从一次更新前 (1000) 行开始,监视扩展事件会话中的lock_escalation事件

lock_escalation在每次更新操作期间出现,所以我开始将每个块1000 -> 500 -> 200 -> 100 -> 50行的行数降低到 1

之前(不是使用这个表,并且对于删除操作 - 不是更新),将行数降低到 200 或 100,有助于摆脱lock_escalation事件
但是这一次,即使每 1 次更新操作有 1 行,表lock_escalation仍然显示。每次更新操作的持续时间大致相同,无论是一次 1 行还是 1000 行

在我的情况下如何摆脱表锁升级?

@@TRANCOUNT 为零

扩展事件:

扩展事件

代码

set nocount on

declare 
    @ChunkSize              int = 1000,                         -- count rows to remove in 1 chunk 
    @TimeBetweenChunks      char(8) = '00:00:01',               -- interval between chunks
    
    @Start                  datetime,
    @End                    datetime,
    @Diff                   int,
    
    @MessageText            varchar(500),
    
    @counter                int = 1,
    @RowCount               int = 1,
    @TotalRowsToUpdate      bigint,
    @TotalRowsLeft          bigint
    


-- total row count to update
set @TotalRowsToUpdate = (select count(*)
                            from [Table1]
                                join [Table2] on
                                    btid = tBtID
                            where   btStatusID = 81)


set @TotalRowsLeft = @TotalRowsToUpdate
set @MessageText = 'Total Rows to Update = ' + cast(@TotalRowsLeft as varchar) raiserror (@MessageText,0,1) with nowait
print ''



-- begin cycle
while @RowCount > 0 begin

    set @Start = getdate()

    -- update packages
    update top (@ChunkSize) bti
        set btstatusid = 154,
            btType = 1
    from [Table1] bti
        join [Table2] on
            btid = tBtID
    where   btStatusID = 81
    

    set @RowCount = @@ROWCOUNT

    -- measure time
    set @End = getdate()
    set @Diff = datediff(ms,@Start,@End)

    set @TotalRowsLeft = @TotalRowsLeft - @RowCount
    set @MessageText = cast(@counter as varchar) + ' - Updated ' + cast(@RowCount as varchar) + ' rows in ' + cast(@Diff as varchar) + ' milliseconds - total ' + cast(@TotalRowsLeft as varchar) + ' rows left...'

    -- print progress message
    raiserror (@MessageText,0,1) with nowait


    set @counter += 1

    WAITFOR DELAY @TimeBetweenChunks

end
Run Code Online (Sandbox Code Playgroud)

计划:

https://www.brentozar.com/pastetheplan/?id=SyozGWMLw

Jos*_*ell 8

如果我们查看实际计划,当前查询正在从要更新的表中读取太多数据。这是来自索引查找BoxTrackInfo

索引查找运算符详细信息的屏幕截图

这是对btid来自BlueTrackEvents. btStatusID在检查行是否符合更新条件时获取更新锁。只有 1,401 行符合更新条件,但在此过程中使用了更多的锁 - 导致锁升级到表级别。

你真的想要一个不同的计划形状 - 在BoxTrackInfo表中寻找btStatusID然后加入到BlueTrackEvents,这应该获得更少的锁。为此,添加这样的索引应该会有所帮助:

CREATE NONCLUSTERED INDEX IX_btStatusID 
ON dbo.BoxTrackInfo (btStatusID)
INCLUDE (btType);
Run Code Online (Sandbox Code Playgroud)

这应该更有效地定位符合条件的行,希望允许更新在没有锁升级的情况下完成。


作为旁注,当前的执行计划验证btStatusID使用合并半连接的外键约束:

显示合并连接 FK 验证的执行计划屏幕截图

在您的情况下,这可能不是什么大问题,因为LBoxTrackStatus表中只有 267 行。如果该表更大,您可能会考虑向查询添加一个LOOP JOINFAST 1提示以获得嵌套循环 FK 验证。有关详细信息,请参阅此帖子:

为什么我在 INSERT 上遇到快照隔离问题?