2015-01-15 35 views
6

假设我有两个不同的线程T1和T2,它们同时访问同一个数据库并从同一个表中获取数据。如何避免两个不同的线程从数据库读取相同的行(Hibernate和Oracle 10g)

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

我该如何避免这种情况?

我目前使用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 2015-01-15 17:09:29

回答

6

你需要在查询时使用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 ...锁定共享模式,或者通过读取某些事务隔离级别中的数据。一致的读取将忽略在读取视图中存在的记录上设置的任何锁定。 (记录 的旧版本不能被锁定;他们通过应用撤消纪录的 内存副本日志重建)

因此,如果T1上的一些行获得的排他锁,T2赢得”无法读取这些记录,直到T1提交或回滚。如果T2使用了一个READ_UNCOMMITTED隔离级别,那么T2永远不会阻塞锁定记录,因为它只是使用撤消日志来重新构建数据,就像查询开始时一样。相对于SQL标准,该Oracke READ_UNCOMMITTED会:

为了提供一个一致的,或不正确,答案,Oracle数据库将 创建一个包含此行,因为它存在时 查询开始块的副本。实际上,Oracle数据库绕着 修改后的数据绕过它,从撤消 (也称为回滚)段重新构建它。返回一致且正确的答案 而不等待事务提交。

+1

好的,谢谢,这是行得通的,但只有当两个线程试图在相近的时间读取相同的数据。如果T1读取数据,并且在1分钟后T2读取相同的数据(其中T1很可能还在处理中),则T2能够看到这些行。 我想锁定行直到它们被更新(例如状态列设置为“OK”或“PROCESSED”)。 此外,是否更新一个实体自动释放行?如果会话关闭或线程崩溃是在某些超时后释放的行? 非常感谢。 – user2799534 2015-01-16 10:26:28

+0

它可能取决于底层数据库独占锁支持。 – 2015-01-16 11:43:05

相关问题