最小化表锁的持续时间

the*_*n B 5 sql-server locking merge

我有一个需要每天更新的 SQL 表。更新发生时可能会或可能不会对该表进行查询。大约有 500,000 行。

我们遇到一个问题,当更新表的作业与针对表的查询同时运行时,会出现锁定冲突。

所以我重写了更新表的流程如下:

ALTER procedure [dbo].[Table_Generate] as

declare @d datetime = getdate(), @c as int

--Check temp tables
IF OBJECT_ID('tempdb..#final') IS NOT NULL
    DROP TABLE #final

IF OBJECT_ID('tempdb..#base') IS NOT NULL
    DROP TABLE #base

--Get source data from linked server
select
    ID, 
    Reference, 
    StartDate, 
    EndDate, 
    Description,
    SomeCode
into #base


from    [LinkedServer].[Database].dbo.[A_View]

--Generate row_hash
select 
    ID, 
    Reference, 
    StartDate, 
    EndDate, 
    Description,
    SomeCode,
    hashbytes('SHA2_256',(
     select
        ID, 
        Reference, 
        StartDate, 
        EndDate, 
        Description,
        SomeCode 
     from #base sub where sub.ID = main.ID for xml raw)) as row_hash
    into #final
    from #base main

select @c = count(*) from #final
if @c >0 begin

merge [The_Table_Staging] as target
using #final as source
on source.ID = target.ID

--New rows
when not matched by target then
insert (    RunDate,
            ID, 
            Reference, 
            StartDate, 
            EndDate, 
            Description,
            SomeCode,
            Row_Hash
        ) values (
            @d,
            source.ID, 
            source.Reference, 
            source.StartDate,
            source.EndDate, 
            source.Description,
            source.SomeCode,
            source.row_hash)

--Existing changed rows
when matched and source.row_hash != target.row_hash then update set
     target.RunDate         = @d
    ,target.Reference       = source.Reference
    ,target.StartDate       = source.StartDate
    ,target.EndDate         = source.EndDate
    ,target.Description     = source.Description
    ,target.SomeCode        = source.SomeCode
    ,target.row_hash        = source.row_hash

--Deleted rows
when not matched by source then delete;

--Existing unchanged rows
update [The_Table_Staging] set RunDate = @d where RunDate != @d

--Commit changes
begin transaction

exec sp_rename 'The_Table_Live', 'The_Table_Staging_T'
exec sp_rename 'The_Table_Staging', 'The_Table_Live'
exec sp_rename 'The_Table_Staging_T', 'The_Table_Staging'

commit transaction
end
Run Code Online (Sandbox Code Playgroud)

其想法是减少不必要的行更新,并最大限度地减少活动表的锁定。我不太喜欢执行表重命名,但执行更新/插入需要 5-10 秒,而表重命名几乎是瞬时的。

所以我的问题是:这种方法可以吗?和/或者我可以改进它吗?

谢谢!

编辑回复JD

嗨,JD,请不必道歉 - 我来这里是为了提出建设性的批评。

  1. 我不知道MERGE有问题。我自己从来没有遇到过问题,但谢谢
  2. 我可以将这部分重写为单独的INSERT/UPDATE/DELETE语句
  3. 我通常同意。我这样做的原因是,如果我TRUNCATE/INSERT在此时进行 from staging,则需要 6-10 秒,而普通版则sp_rename需要不到一秒。因此锁定表的时间更少
  4. 这不会影响表锁定,因为它首先将数据放入临时表中。我别无选择,只能使用链接服务器或 SSIS,在这种情况下,我更喜欢链接服务器将所有 SQL 保存在一个地方
  5. 我总是使用XML而不是CONCAT因为否则'a','bc'将散列与'ab','c'相同,这是不正确的

从暂存到填充活动表的所有处理都很好 - 我只是想最大限度地减少最终活动表被锁定的时间,

J.D*_*.D. 8

不幸的是,我一眼就看到你的代码中有很多错误:

  1. 有大量已知的错误表明MERGE生产代码中确实应该完全避免它。

  2. MERGE众所周知,其性能不如编写单独的INSERTUPDATEDELETE语句;这可能是一些阻塞问题的原因。

  3. 虽然很诱人,但使用该sp_rename函数来最小化阻塞问题实际上可能会导致更严重的阻塞问题,正如 Kendra Little 的《为什么你应该在暂存表中切换而不是重命名它们》中所讨论的那样。(如果我没记错的话,本文讨论了使用分区切换作为更好的解决方案。)

  4. 众所周知,链接服务器有时也会成为性能瓶颈(对于固定基数估计以及在处理之前将所有数据传输到网络)。如果您之前在代码事务中依赖它,则可以将该部分保留在事务之外,以最大限度地减少本地表的锁定时间。

  5. 另外,我确实喜欢使用该HASHBYTES()函数来生成行哈希,这是我过去使用过的。CONCAT()但您也可能会发现,通过在每列上使用安全列分隔符(例如双管道||)作为参数来在行本身上调用它,而不是在利用 XML 的子查询中使用它,性能会更高。

  6. 话虽如此,正如 Erik Darling 所指出的那样,HASHBYTES()它本身有时很容易受到大规模性能问题的影响。如果这是您的瓶颈,您可以尝试在计算列或索引视图中具体化函数的结果(它是确定性的),或者使用替代方法,例如 CLR,如链接文章中所述。