提问者:小点点

事务隔离-INSERTS依赖于以前的记录值


这个问题与另一件事的讨论有关:订单标题-订单行事务的正确隔离级别是什么?

想象一下,我们有通常的Orders_Headers和Orders_LineItems表。假设我们有一个特殊的业务规则,它说:

>

  • 每个订单都有折扣字段,该字段根据上次输入订单的时间计算

    如果过去Y小时内有超过X个订单,则会特别计算每个下一个订单折扣字段。

    如果最近10个订单的平均频率高于每分钟x,则会特别计算每个下一个订单折扣字段。

    每次下单折扣字段特殊计算

    这里的要点是表明每个订单都依赖于以前的订单,隔离级别至关重要。

    我们有一个事务(只是显示的代码逻辑):

    BEGIN TRANSACTION
    
    INSERT INTO Order_Headers...
    
    SET @Id = SCOPE_IDENTITY()
    
    INSERT INTO Order_LineItems...(using @Id)
    
    DECLARE @SomeVar INT
    
    --just example to show selecting previous x orders
    --needed to calculate Discount value for new Order
    SELECT @SomeVar = COUNT(*) Order_Headers
    WHERE ArbitraryCriteria
    
    UPDATE Order_Headers
    SET Discount= UDF(@SomeVar)
    WHERE Id = @Id
    
    COMMIT
    
    END TRANSACTION
    

    我们还有另一个事务来读取订单:

    SELECT TOP 10 * FROM Order_Headers
    ORDER BY Id DESC
    

    问题

    >

  • 第一个事务的SNAPSHOT隔离级别和第二个适当级别的READ COMMITED是否合适?

    是否有更好的方法来处理CREATE/UPDATE事务,或者这是一种方法?


  • 共2个答案

    匿名用户

    快照的问题不在于插入/读取(我假设你决定使用)。这是关于更新,你应该关心。

    快照隔离级别使用行版本控制。这意味着每当您插入/更新/删除行时,这些行都会在temdb中重复(版本存储,此类行的位置),并使用版本控制标记将其大小增加14个字节,以便您新启动的事务可以从上次提交的事务中读取一行。请记住,这些调整大小的行将保持不变,直到您重建索引。

    这应该是一个指标,如果您的表真的很忙,您的索引将更快地进行碎片整理,并且它将在您的temp.So上增加一定数量的%开销。

    正如我提到的,这里更令人关切的是更新。

    每当您插入/删除/更新行时,您将在这些行上获得排他锁(稍后为对象),并且由于您的快照使用行版本控制,因此来自另一个事务的插入会在新行上添加排他锁,这不是问题。但是,如果您尝试更新现有行并且会话2尝试在该行上获取X锁,它将失败,因为会话1已经有X锁,这就是您将获得以下消息的地方:

    读已提交和Serializable已经很好地解决了这些问题,因此您可能需要采取这种方法并在实际实施之前测试所有解决方案。请记住,所有事务都会导致更新阻塞,快照/读已压缩快照只会失败。

    我个人会使用读取已提交的快照和更改的过程,在catch块中重新运行N次,但嘿,这也有缺陷!

    匿名用户

    可序列化选项:

    通过updlock可序列化表提示使用悲观锁定策略来获取由where条件指定的键范围锁(由支持索引支持以仅锁定查询所需的范围):

    declare @Id int, @SomeVar int;
    begin tran;
    
      select @SomeVar = count(OrderDate) 
      from Order_Headers with (updlock,serializable) 
      where OrderDate >= '20170101';
    
      insert into Order_Headers (OrderDate, SomeVar)
        select sysdatetime(), @SomeVar;
    
      set @Id = scope_identity();
    
      insert into Order_LineItems (id,cols)
        select @Id, cols
        from @TableValuedParameter;
    
    commit tran;
    

    一个很好的指南,为什么以及如何使用updlock可序列化表提示来使用select锁定键范围,以及为什么需要两者,在Sam Saffron的upput(更新/插入)模式中进行了介绍。

    参考:

    • 关于可序列化和其他表提示的文档-MSDN
    • 密钥范围锁定-MSDN
    • SQL服务器隔离级别:A系列-Paul White
    • 关于T-SQL事务隔离级别的问题,你太害羞了-Robert Sheldon
    • 由Brent Ozar策划的隔离级别参考