【问题标题】:What happens if 2 or more people update a record at the exact same time?如果 2 个人或更多人同时更新记录会发生什么?
【发布时间】:2011-11-08 23:59:58
【问题描述】:

我使用 NHibernate 和 version 属性,每次更新我的聚合根时都会自动递增。如果 2 人或更多人同时更新同一条记录会怎样?

另外,我将如何测试这个?

请注意,这不是我遇到的情况,只是想知道。

【问题讨论】:

    标签: c# sql-server nhibernate concurrency


    【解决方案1】:

    什么是原子的,什么不是

    正如其他人所说,SQL Server 中的更新是原子操作。但是,当使用 NHibernate(或任何 O/RM)更新数据时,您通常首先select 数据,对对象进行更改,然后update 数据库进行更改。该事件序列不是原子的。即使选择和更新是在几毫秒内执行的,另一个更新也有可能在中间滑落。如果两个客户端获取相同数据的相同版本,如果他们假设当时只有他们编辑该数据,他们可能会不知不觉地覆盖彼此的更改。

    问题说明

    如果我们不防范这种并发更新情况,可能会发生奇怪的事情 - 看似不可能的鬼鬼祟祟的错误。假设我们有一个模拟水状态变化的类:

    public class BodyOfWater
    {
        public virtual int Id { get; set; }
        public virtual StateOfMatter State { get; set; }
    
        public virtual void Freeze()
        {
            if (State != StateOfMatter.Liquid)
                throw new InvalidOperationException("You cannot freeze a " + State + "!");
            State = StateOfMatter.Solid;
        }
    
        public virtual void Boil()
        {
            if (State != StateOfMatter.Liquid)
                throw new InvalidOperationException("You cannot boil a " + State + "!");
            State = StateOfMatter.Gas;
        }
    }
    

    假设数据库中记录了以下水体:

    new BodyOfWater
    {
        Id = 1,
        State = StateOfMatter.Liquid
    };
    

    两个用户大致同时从数据库中获取此记录,对其进行修改,然后将更改保存回数据库。用户 A 冻结水:

    using (var transaction = sessionA.BeginTransaction())
    {
        var water = sessionA.Get<BodyOfWater>(1);
        water.Freeze();
        sessionA.Update(water);
    
        // Same point in time as the line indicated below...
    
        transaction.Commit();
    }
    

    用户 B 试图烧开水(现在是冰!)...

    using (var transaction = sessionB.BeginTransaction())
    {
        var water = sessionB.Get<BodyOfWater>(1);
    
        // ... Same point in time as the line indicated above.
    
        water.Boil();
        sessionB.Update(water);
        transaction.Commit();
    }
    

    ...并且成功了!!!什么?用户 A 冻结了水。不应该抛出一个异常说“你不能煮一个固体!”吗?用户 B用户 A 保存他的更改之前获取了数据,因此对于两个用户来说,水最初似乎是液体,因此两个用户都可以保存他们相互冲突的状态更改。

    解决方案

    为防止出现这种情况,我们可以在类中添加一个Version 属性,并在NHibernate 中使用&lt;version /&gt; 映射对其进行映射:

    public virtual int Version { get; set; }
    

    这只是 NH​​ibernate 每次更新记录时都会增加的一个数字,它会检查以确保在我们不观看时没有其他人增加版本。而不是像...这样的并发简单的 sql 更新。

    update BodyOfWater set State = 'Gas' where Id = 1;
    

    ... NHibernate 现在将使用更智能的查询,如下所示:

    update BodyOfWater set State = 'Gas', Version = 2 where Id = 1 and Version = 1;
    

    如果受查询影响的行数为 0,那么 NHibernate 就知道出了问题 - 或者其他人更新了该行以使版本号现在不正确,或者有人删除了该行以使该 Id 不再存在。 NHibernate 然后会抛出一个StaleObjectStateException

    关于网络应用的特别说明

    数据的初始select 和随后的update 之间的时间越长,出现此类并发问题的可能性就越大。考虑 Web 应用程序中的典型“编辑”表单。从数据库中选择实体的现有数据,放入 HTML 表单,然后发送到浏览器。用户可能会花几分钟时间修改表单中的值,然后再将其发送回服务器。很有可能其他人正在同时编辑相同的信息,并且他们在我们之前保存了他们的更改。

    确保版本在我们实际保存更改的几毫秒内不会更改,在这种情况下可能还不够。为了解决这个问题,您可以将版本号作为隐藏字段与其他表单字段一起发送到浏览器,然后在保存之前从数据库中取回实体时检查以确保版本没有更改.此外,您可以限制初始select 和最终update 之间的时间量,方法是提供单独的“视图”和“编辑”视图,而不仅仅是对所有内容使用“编辑”视图。用户在“编辑”视图上花费的时间越少,他们看到令人讨厌的错误消息的机会就越小,说明他们的更改无法保存。

    【讨论】:

      【解决方案2】:

      简单地说:他们不能。更新按顺序处理。每个更新都是 - 或者至少应该是 - 原子的。因此,该属性增加了两次。

      【讨论】:

        【解决方案3】:

        在更新行之前,您必须拥有该行的锁。 SQL Server 以原子方式锁定行。也就是说,只有一个竞争进程可以获得锁。所有其他潜在的声明者都必须等待锁被释放。

        【讨论】:

          【解决方案4】:

          取决于在 SQL Server 中使用事务(如果使用)时隔离级别的设置方式。 (虽然“精确同时”的记录编辑在技术上是不可能的)

          有关这方面的一些基本信息,请访问Concurrency Series: Basics of Transaction Isolation Levels

          【讨论】:

            【解决方案5】:

            正如 Mike Adler 所说,更新是按顺序处理的。但是一个会失败,我认为它会通过抛出一个陈旧的对象异常来做到这一点,因为如果版本是过滤器的一部分来更新行。

            MyTable  
            Id | RowVersion | Description  
            1  | 1          | this description
            

            SQL:
            第一次更新
            更新 MyTable set description = 'test', rowversion=2 where id = 1 and rowversion = 1

            结果

            MyTable  
            Id | RowVersion | Description  
            1  | 2          | test
            

            第二次更新
            Update MyTable set description = 'second update', rowversion=2 where id = 1 and rowversion = 1

            没有更新。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2016-09-06
              • 1970-01-01
              • 1970-01-01
              • 2011-04-19
              • 2014-04-28
              • 2023-03-21
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多