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

2022-09-03 15:36:33

假设我有两个不同的线程,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

您需要在查询时使用: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 的工作原理如下:

选择。。。FOR UPDATE 锁定行和任何关联的索引项,这与为这些行发出 UPDATE 语句相同。其他事务被阻止更新这些行,执行 SELECT ...锁定在共享模式,或从读取特定事务隔离级别中的数据。一致性读取将忽略在读取视图中存在的记录上设置的任何锁。(无法锁定记录的旧版本;通过在记录的内存中副本上应用撤消日志来重建这些版本。

因此,如果 T1 在某些行上获取了独占锁,则在 T1 提交或回滚之前,T2 将无法读取这些记录。如果 T2 使用READ_UNCOMMITTED隔离级别,则 T2 永远不会阻塞锁定记录,因为它只是使用撤消日志来重建数据,就好像查询开始时一样。与SQL标准相反,Oracle READ_UNCOMMITTED将:

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


答案 2

推荐