如果2个或更多人在同一时间更新记录会发生什么?

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

另外,我该怎么测试呢?

请注意,这不是我一直在进行的情况,只是想知道。

什么是primefaces,什么不是

正如其他人所说,SQL Server中的更新是primefaces操作。 但是,使用NHibernate(或任何O / RM)更新数据时,通常首先select数据,对对象进行更改,然后使用更改update数据库。 这一系列事件不是primefaces的。 即使选择和更新在彼此之间的毫秒内执行,也存在另一次更新在中间滑动的可能性。 如果两个客户端获取相同版本的相同数据,如果他们认为他们是当时唯一编辑该数据的人,他们可能会无意中覆盖彼此的其他更改。

问题图

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

 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(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(1); // ... Same point in time as the line indicated above. water.Boil(); sessionB.Update(water); transaction.Commit(); } 

……并且成功了!!! 什么? 用户A冻结了水。 不应该抛出一个例外,说“你不能煮沸!”? 用户B 用户A保存其更改之前获取了数据,因此对于两个用户来说,水似乎最初是流动的,因此允许两个用户保存他们的冲突状态更改。

为了防止出现这种情况,我们可以在类中添加一个Version属性,并使用映射将其映射到NHibernate中:

 public virtual int Version { get; set; } 

这只是一个NHibernate每次更新记录时都会递增的数字,它会检查以确保没有其他人在我们不看的时候增加了版本。 而不是像并发一样的并发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

关于Web Apps的特别说明

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

在这样的场景中,确保版本在几毫秒内没有改变我们实际上保存更改可能是不够的。 要解决此问题,您可以将版本号作为隐藏字段与其余表单字段一起发送到浏览器,然后检查以确保在保存之前从数据库中取回实体时版本未更改。 此外,您可以通过提供单独的“视图”和“编辑”视图来限制初始select和最终update之间的时间量,而不是仅使用“编辑”视图。 用户花在“编辑”视图上的时间越少,他们就会越少有机会收到一条恼人的错误消息,说明他们的更改无法保存。

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

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

取决于在SQL Server使用事务(如果使用)时如何设置隔离级别。 (虽然技术上不可能进行“完全相同的时间”记录编辑)

有关此内容的一些基本信息可在并发系列:事务隔离级别基础知识中找到

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

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

SQL:
第一次更新
更新MyTable set description =’test’,rowversion = 2,其中id = 1,rowversion = 1

结果

 MyTable Id | RowVersion | Description 1 | 2 | test 

第二次更新
更新MyTable set description =’second update’,rowversion = 2其中id = 1且rowversion = 1

没有更新。