如何避免两个不同的线程从数据库(Hibernate和Oracle 10g)读取相同的行

15

假设我有两个不同的线程T1和T2,同时访问同一数据库并从同一张表中提取数据。

现在在线程启动时,我需要从表中获取数据并将行存储到集合中,然后在其他地方使用该集合执行一些工作。我不希望这两个线程能够处理相同的数据,因为这将导致重复(而且很长)的工作。更具体地说,这是一个企业应用程序,需要在启动时加载一些记录并将其存储在集合中以执行一些额外的工作。问题是,在群集环境中,这可能会导致两个不同的实例加载相同的数据,因此工作可能会重复。因此,我希望行只由单个实例加载一次。

如何避免这种情况?

我目前正在使用Hibernate和Oracle 10g。这些是我迄今为止的解决方案:

  • 通过编程锁定行。第一个读取它的人将某个“锁定”列设置为true,但如果第一个线程死亡而没有将行设置为“处理”,则非常容易发生死锁。

  • 使用悲观锁。我尝试使用LockMode.UPGRADE,但似乎无济于事,因为我仍然能够同时从两个线程读取数据。

public List<MyObject> getAllNtfFromDb() {
      Session session = HibernateUtil.getOraclesessionfactory().openSession();
      Query q = session.createQuery(
              "from MyObject n where n.state = 'NEW'");
    List<MyObject> list = (List<MyObject>) q.list();
      for (int i=0; i<list.size(); i++)
          session.lock(list.get(i), LockMode.UPGRADE);
return list;
}
还有其他提示吗?我做错了什么吗?
谢谢。

1
也许你可以使用select for update语句? - soilworker
你能否提供在此行代码中使用的Hibernate导入语句: Session session = HibernateUtil.getOraclesessionfactory().openSession(); - Orby
1个回答

9

你需要在查询时使用 PESSIMISTIC_WRITE

Query q = session
    .createQuery("from MyObject n where n.state = 'NEW'")
    .setLockOptions(new LockOptions(LockMode.PESSIMISTIC_WRITE));
List<MyObject> list = (List<MyObject>) q.list();

锁定父对象就足够了,不一定会发生死锁。如果持有锁的线程在另一个线程超时等待之前没有释放锁,则可能会出现锁获取失败。
由于您正在使用Oracle,这是SELECT FOR UPDATE的工作原理:

SELECT ... FOR UPDATE锁定行和任何相关的索引条目,就像您为这些行发布UPDATE语句一样。其他事务被阻止更新这些行,从执行SELECT ... LOCK IN SHARE MODE或从读取某些事务隔离级别中的数据。一致性读取忽略在读取视图中存在的记录上设置的任何锁定。(记录的旧版本不能被锁定;它们通过在记录的内存副本上应用撤消日志来重建。)

如果T1对某些行获取了独占锁,那么在T1提交或回滚之前,T2将无法读取这些记录。如果T2使用READ_UNCOMMITTED隔离级别,则T2永远不会阻塞于锁定记录,因为它仅使用撤销日志来重建数据,就好像查询开始时的情况一样。与SQL标准相反,Oracle READ_UNCOMMITTED将执行以下操作:

为了提供一致或正确的答案,Oracle数据库将创建包含此行的块的副本,该块在查询开始时存在...实际上,Oracle数据库绕过了修改后的数据-它读取了周围的数据,并从撤消(也称为回滚)段中重构了数据。一个一致和正确的答案返回,而无需等待事务提交。


1
好的,谢谢。这确实有效,但只有在两个线程在非常接近的时间内尝试读取相同数据时才有效。如果T1读取数据并且1分钟后T2读取相同数据(T1很可能仍在处理),那么T2就能看到这些行。我想锁定该行,直到它们被更新(例如,状态列设置为“OK”或“PROCESSED”)。此外,更新一个实体是否会自动释放该行?如果会话关闭或线程崩溃,行是否会在一段时间后被释放?非常感谢。 - user2799534
这可能取决于底层数据库的独占锁支持。 - Vlad Mihalcea

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接