春季默认@Transactional和默认的丢失更新

在spring的环境中有一个大的现象,或者我非常错误。 但默认的spring @Transactional注释不是ACID,只有ACD缺乏隔离。 这意味着如果你有方法:

@Transactional public TheEntity updateEntity(TheEntity ent){ TheEntity storedEntity = loadEntity(ent.getId()); storedEntity.setData(ent.getData); return saveEntity(storedEntity); } 

如果2个线程以不同的计划更新进入,会发生什么。 它们都从db加载实体,它们都应用自己的更改,然后第一个被保存并提交,第二个被保存并提交第一个UPDATE IS LOST。 那是真的吗? 使用调试器它就是这样工作的。

你不是非常错,你的问题是一个非常有趣的观察。 我相信(基于你的评论)你在非常具体的情况下考虑它,而这个主题更广泛。 让我们一步一步来。

在ACID中的确代表着孤立。 但这并不意味着需要一个接一个地执行两个或更多个事务。 他们只需要被隔离到某种程度。 大多数关系数据库允许在事务上设置隔离级别,甚至允许您从其他未提交的事务中读取数据。 如果这种情况好或没有,这取决于具体的应用。 请参阅mysql文档:

https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html

您当然可以将隔离级别设置为可序列化并实现您的期望。

现在,我们还有不支持ACID的NoSQL数据库。 最重要的是,如果您开始使用数据库集群,您可能需要拥抱数据的最终一致性,这甚至可能意味着在执行读取时,刚写入某些数据的相同线程可能无法接收它。 同样,这是一个非常具体的特定应用程序的问题 – 我能否承受一段时间不一致的数据以换取快速写入?

您可能倾向于在银行业务或某些金融系统中以可序列化的方式处理一致的数据,您可能会在社交应用程序中使用不太一致的数据,但实现更高的性能。

更新丢失了 – 就是这样吗?

是的,情况就是这样。

我们害怕可序列化吗?

是的,它可能会变得讨厌:-)但重要的是要了解它是如何工作的以及后果是什么。 我不知道是否仍然如此,但我在大约10年前的一个项目中遇到过使用DB2的情况。 由于非常具体的情况,DB2在整个表上执行锁升级到独占锁,有效阻止任何其他连接访问表甚至读取。 这意味着一次只能处理一个连接。

因此,如果您选择使用可序列化级别,则需要确保您的事务实际上很快并且实际上需要它。 也许在你写作的时候其他一些线程正在读取数据呢? 试想一下你的文章有评论系统的场景。 突然,一篇病毒式文章发布,每个人都开始发表评论。 用于注释的单个写事务需要100ms。 100个新的评论交易排队,这将有效地阻止阅读未来10年的评论。 我确信在这里继续读取将完全足够,并允许您实现两件事:更快地存储注释并在编写时读取它们。

长话短说:这完全取决于您的数据访问模式,并且没有银弹。 有时可序列化是必需的,但它有性能损失,有时读取未提交将是好的,但它会带来不一致的处罚。

丢失数据?

你没有丢失数据。 可以把它想象成改变代码中的变量。

 int i = 0; i = 5; i = 10; 

你“失去了”5吗? 好吧,不,你替换了它。

现在,您提到的multithreading的棘手部分是,如果这两个SQL更新同时发生?

从纯粹的更新角度来看(忘记阅读),它没有什么不同。 数据库将使用锁来序列化更新,因此一个仍将在另一个之前。 第二个自然胜出。

但是,这里有一个危险……

根据当前状态更新

如果更新是基于当前状态的条件怎么办?

 public void updateEntity(UUID entityId) { Entity blah = getCurrentState(entityId); blah.setNumberOfUpdates(blah.getNumberOfUpdates() + 1); blah.save(); } 

现在你有一个数据丢失的问题,因为如果两个并发线程执行读取( getCurrentState ),它们将各自添加1 ,到达相同的数字,第二个更新将失去前一个的增量。

解决它

有两种解决方案。

  1. 可序列化隔离级别 – 在大多数隔离级别中,读取( select s)不包含任何排它锁,因此不会阻塞,无论它们是否在事务中。 Serializable实际上会为每个读取的行获取并保持一个独占锁,并且只在事务提交或回滚时释放这些锁。
  2. 在单个语句中执行更新。 – 单个UPDATE语句应该为我们创建这个primefaces,即UPDATE entity SET number_of_updates = number_of_updates + 1 WHERE entity_id = ?

一般来说,后者更具可扩展性。 您持有的锁越多,持有的时间越长,获得的阻塞就越多,因此吞吐量就越低。