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

问题描述 投票:8回答:1

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

还有其他提示吗?我究竟做错了什么?

谢谢。

java oracle hibernate transactions locking
1个回答
7
投票

您需要在查询时使用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标准相反,Oracke READ_UNCOMMITTED将:

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

© www.soinside.com 2019 - 2024. All rights reserved.