【问题标题】:Race condition though using transactions使用事务的竞争条件
【发布时间】:2012-05-15 09:20:04
【问题描述】:

我有这笔交易:

em.getTransaction().begin();
{
    final Payment payment = em.find(Payment.class, id);
    if (payment.status != Status.INIT)
        throw new IllegalStateException("Cannot set to PAID, is not INIT but " + status);

    payment.status = Status.PAID;

}
em.getTransaction().commit();
log.info("Payment " + id + " was paid");

但是,正如您在此处看到的,事务不会阻止竞争条件:

[11:10:18.265] INFO  [PaymentServlet] [MSP] Status COMPLETED 
[11:10:18.265] INFO  [PaymentServlet] Payment c76f9e75-99d7-4721-a8ac-e3a638dd8317 was paid 
[11:10:18.267] INFO  [PaymentServlet] [MSP] Status COMPLETED 
[11:10:18.267] INFO  [PaymentServlet] Payment c76f9e75-99d7-4721-a8ac-e3a638dd8317 was paid 

两次付款设置为PAID。我的异常没有抛出,也没有回滚什么的。

我做错了什么?

【问题讨论】:

  • 付款中有@Version 字段吗?
  • @Anonymoose 不,我在任何地方都没有特殊的并发性。我认为这就是交易的目的。
  • 这是你的“特殊并发”。
  • @BartvanHeukelom 不,在这种情况下,事务并不意味着处理并发。为了防止并发编辑,您需要一些锁定机制,添加版本字段是一种方法(称为乐观锁定)。

标签: java hibernate jpa transactions race-condition


【解决方案1】:

您需要使用乐观锁定。乐观锁定是冲突更新很少发生的地方,因此可以在偶然事务发生时回滚它。悲观锁定导致数据库在对象使用时对其进行锁定,从而有效地将所有内容单线程处理并可能导致性能问题。更详细的解释见http://en.wikibooks.org/wiki/Java_Persistence/Locking#JPA_2.0_Locking

要解决这里的问题,你应该在 Payment 中添加一个字段(传统的声明是 private Long 版本)并给它 JPA @Version 注解。如果您手动管理架构,请确保相应的列存在于正确的表中。然后,JPA 将使用此字段来检查冲突更新并在存在冲突时回滚事务。

更新:关于悲观锁定的更多信息:https://blogs.oracle.com/carolmcdonald/entry/jpa_2_0_concurrency_and 简而言之,您可以配置 JPA 来锁定对象,但这样做是个好主意的情况非常少见。换句话说,如果你是手动编写 JDBC 查询,你必须在每次选择的末尾写上“for update”来导致悲观锁定;默认是不锁定读取,因为这会让数据库和数据库用户哭泣。

【讨论】:

  • 但是,它不应该在此处自动应用悲观(或实际上任何一种)锁定吗?现在,交易并没有做我期望它做的一件事:保持完整性。我对交易的理解是错误的吗? (我现在必须更新我所有的 JPA 代码吗?XD)
  • 实践中没有。酸是非常昂贵的。通常默认的“可重复读取”级别的隔离通常是一个很好的权衡,原因与乐观锁定很少导致回滚完全相同,即除非被激起,否则冲突很少见。不过,您仍然可以获得原子性(全有或全无)、一致性(整个数据库不会失效)和持久性(提交不会消失)。大多。再次,权衡。
  • 您可能会将 ACID 与实现它的更常见技术之一混淆:严格的两阶段锁定 (S2PL)。与乐观并发控制 (OCC) 相比,可序列化快照隔离在高争用下的回滚次数更少,没有 S2PL 的阻塞和死锁。
  • 特意总结一下,谢谢指出。
【解决方案2】:

你没有说你正在使用什么数据库,或者什么事务隔离级别。如果您使用符合 SQL 标准的 SERIALIZABLE 事务,您将不会看到此错误。 PostgreSQL 9.1 之前的版本,MS SQL Server 的一些配置,以及所有版本的 Oracle 在你请求的时候都不会给你真正的可序列化事务,所以在这样的环境中必须使用显式锁。大多数数据库产品默认为READ COMMITTED 事务隔离级别,因此您可能需要显式请求SERIALIZABLE 事务。

完全披露,我曾与 Dan R.K. MIT 的端口将真正的可序列化事务添加到 PostgreSQL 版本 9.1,以便 Wisconsin Courts 软件可以干净地处理这些问题。有关差异的示例,请参阅this Wiki page

【讨论】:

  • 是PostgreSQL 9.0.5,我没有明确设置任何隔离级别。
  • 那么你必须使用某种形式的显式锁定。选项包括另一个答案中描述的乐观锁定、建议锁定、显式表锁定或在应用程序级别编码的许多其他解决方案中的任何一种。或者您可以升级到 9.1 并使用 SERIALIZABLE 事务。 PostgreSQL 文档中的这一章可能会有所帮助:postgresql.org/docs/9.1/interactive/mvcc.html
  • 升级可能是可能的,我会调查的。顺便问一下,在 9.1 之前请求 Serializable 会得到什么,为什么不抛出“我不太支持这个”的错误?
猜你喜欢
  • 1970-01-01
  • 2016-11-16
  • 1970-01-01
  • 2015-09-10
  • 2017-07-24
  • 2021-12-07
  • 1970-01-01
  • 1970-01-01
  • 2012-01-26
相关资源
最近更新 更多