假设我有两个不同的线程,T1和T2,同时访问同一个数据库并从同一个表中获取数据。
现在在线程启动时,我需要从表中获取数据并将行存储到一个集合中,然后我将用它来执行其他工作。我不希望这两个线程能够处理相同的数据,因为它会导致重复(和长期)工作。更具体地说,这是一个企业应用程序,需要在启动时加载一些记录并将其存储在一个集合中以执行一些额外的工作。问题是在集群环境中,这可能导致两个不同的实例加载相同的数据,因此可以复制工作。所以我希望行只能由一个实例加载一次。
我该如何避免这种情况?
我目前正在使用Hibernate和Oracle 10g。这些是我迄今为止的解决方案:
以编程方式锁定行。读取它的第一个设置了一些"锁定" column为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; }
还有其他提示吗?我究竟做错了什么?
感谢。
答案 0 :(得分: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语句一样。 阻止其他事务更新这些行 选择...锁定共享模式,或者从某些数据中读取数据 事务隔离级别。一致性读取忽略设置的任何锁定 读取视图中存在的记录。 (旧版本的记录 无法锁定;它们是通过在一个上应用撤消日志来重建的 内存中的记录副本。)
因此,如果T1在某些行上获得了独占锁定,则在T1提交或回滚之前,T2将无法读取这些记录。如果T2使用READ_UNCOMMITTED隔离级别,则T2将不会阻塞锁定记录,因为它只是使用撤消日志来重建数据,就像查询开始时一样。与SQL标准相反,Oracke READ_UNCOMMITTED将:
为了提供一致或正确的答案,Oracle数据库将会 创建包含此行的块的副本,因为它存在时 查询开始...实际上,Oracle数据库绕道而行 修改后的数据 - 它读取它,从撤消中重建它 (也称为回滚)段。一致和正确的答案 无需等待事务提交即可返回。