【问题标题】:Hibernate two simultaneous transactions on different machines休眠不同机器上的两个同时事务
【发布时间】:2012-09-12 20:04:14
【问题描述】:

我在远程网络服务器上有一个订购平台,在我的办公室有一台本地生产机器。 两个网络服务器都访问同一个远程 MySQL (InnoDB) 数据库。

我的问题:生产需要几分钟,其中有 2-3 笔交易是开放的。在这个时候,我生成新的发票号码并增加它们。最近的发票编号保存在数据库的 Numbers-Table 中。

public Long getNewInvoiceNumber() {
    Criteria crit = getSession().createCriteria(Numbers.class);
    Numbers n = ((Numbers)crit.uniqueResult());
    Long newNumber = n.getInvoiceNumber() + 1L;
    n.setInvoiceNumber(newNumber);
    return newNumber;
}

现在,当有人在生产过程中保存新订单时,他们正在访问相同的 Numbers-Table 以生成另一个编号(而不是发票)。 生产处理的所有订单都保存有正确的发票编号。 但是,Numbers-Table 不会更新为最新值,并且发票编号与生产前保持一致。

我了解其中一项事务收到“陈旧表”消息。 但是 MySQL / Hibernate / Java 的行为是什么?我想从其中一个事务中得到一个异常,这样我就可以回滚并且没有这种危险的数据库不一致。

编辑: 这就是表格Numbers 的样子:

id | invoice_number | tag_number
0  | 16533          | 1055

id 是主键。我只访问这一行并增加所需的数量。

编辑 2: 好的,我看到这个表结构有点糟糕。 我将其更新为:

id             | number
invoice_number | 16533
tag_number     | 1055

现在我可以独立访问每一行。不知道这是否能解决我的问题。

【问题讨论】:

  • 您的 Numbers.class 表是否只有 1 行?查看 setLockMode() / setLockOptions() 的 API。您需要 SELECT ... FOR UPDATE 以确保没有其他用户在做同样的事情。你还需要 getSession().update(n);对吗?
  • 是的,到目前为止只有 1 行。我以为数据库有自己的自动锁定模式,我想知道如果有 2 个用户同时更改行的行为。
  • 两个独立用户可以同时查看来自两个不同事务的相同或旧数据。为了在相对较少的情况下停止这种情况 SELECT ... FOR UPDATE 存在。我会更多地研究这个主题并使用命令行 SQL 客户端测试该理论。

标签: mysql hibernate transactions


【解决方案1】:
public Long getNewInvoiceNumber() {
    Criteria crit = getSession().createCriteria(Numbers.class);
    crit.setLockMode(LockMode.PESSIMISTIC_WRITE);   // LINE ADDED
    Numbers n = ((Numbers)crit.uniqueResult());
    Long newNumber = n.getInvoiceNumber() + 1L;
    n.setInvoiceNumber(newNumber);
    getSession().update(n);              // LINE ADDED
    return newNumber;
}

如果使用 MySQL,则必须使用 InnoDB,应确保 Hibernate 将“FOR UPDATE”添加到为 crit.uniqueResult() 部分生成的 SQL 中。我还会在之后设置一个 Java 断点(停止执行)并手动测试相同的 SQL 查询会导致另一个客户端阻塞。

这更多是为了测试您的 SQL 服务器是否正常,您的方言设置是否正确,并且该功能基本上适合您。

这会在生成下一个 InvoiceNumber 时强制在 SQL 服务器上进行序列化。

这样,即使 1000 个用户同时尝试开具发票,您也不会得到 2 个相同的 InvoiceNumber。

注意:LockOptions 替换 LockMode 以指定有关并发这一方面的模式。请参阅 hibernate 发行说明以获取帮助。

注意:您谈到了持续几分钟的交易。当交易未提交时,其他用户无法生成新的发票编号。也许您需要将其作为嵌套和单独的事务运行。然后,如果由于其他一些处理错误而无法使用该号码,则 InvoiceNumber 中会出现问题漏洞。会计师/会计系统不喜欢发票号码上的漏洞,也不喜欢它们的顺序过时。

相关 MySQL 文档链接https://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html

【讨论】:

    【解决方案2】:

    有不同的方法可以同时使用数据库,例如使用乐观锁或悲观锁(你应该小心后者,因为它可能会导致延迟和死锁)。

    但我想在您的情况下,您必须在某些事务开始后立即更改 Numbers 表,而不是在它结束时。这将确保为发票预留空位。

    查看 Hibernate 的 Id Generators 也很有意义,您可以看到关于如何实现值生成机制的不同想法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-11-25
      • 2015-07-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多