我有两个并发事务执行这段代码(为简化说明而简化):
@Transactional
public void deleteAccounts() {
List<User> users = em.createQuery("select u from User", User.class)
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.getResultList();
for (User user : users) {
em.remove(user);
}
}
我的理解是其中一个事务,比如事务A,应首先执行SELECT,锁定它需要的所有行,然后继续使用DELETE,而另一个事务应该在执行SELECT之前等待A的提交。但是,这段代码是死锁的。我哪里错了?
答案 0 :(得分:1)
USER表可能有很多外键引用它。如果其中任何一个未编入索引,Oracle将锁定整个子表,同时从父表中删除该行。如果多个语句同时运行,即使对于不同的用户,也会锁定相同的子表。由于无法控制那些递归操作的顺序,因此多个会话可能会以不同的顺序锁定相同的资源,从而导致死锁。
有关详细信息,请参阅概念手册中的this section。
要解决此问题,请将索引添加到任何未编制索引的外键。如果列名是标准的,那么这样的脚本可以帮助您找到潜在的候选者:
--Find un-indexed foreign keys.
--
--Foreign keys.
select owner, table_name
from dba_constraints
where r_constraint_name = 'USER_ID_PK'
and r_owner = 'THE_SCHEMA_NAME'
minus
--Tables with an index on the relevant column.
select table_owner, table_name
from dba_ind_columns
where column_name = 'USER_ID';
答案 1 :(得分:0)
当您使用PESSIMISTIC_WRITE JPA时,通常会将其转换为SELECT FOR UPDATE,这会对数据库进行锁定,对于依赖于数据库的行以及如何配置锁定是不必要的,默认情况下,锁定是按页面或块进行的不是为了行,所以请检查数据库文档以确认数据库如何进行锁定,也可以更改它以便可以将锁应用于行。 当你调用deleteAccounts方法时,它会启动一个新的事务,并且锁定将一直处于活动状态,直到事务提交(或回滚),在这种情况下,当方法完成时,如果其他事务想要获取相同的锁,它就不能和我认为这就是为什么你有死锁,我建议你尝试另一种机制,可能是一个乐观的锁,或一个实体锁。
您可以尝试给予获取锁定的超时时间:
em.createQuery("select u from User", User.class)
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.setHint("javax.persistence.lock.timeout", 5000)
.getResultList();
我找到了一个很好的article来解释这个错误,这是由数据库导致的:
Oracle自动检测死锁并通过滚动解决它们 因此,返回死锁中涉及的一个事务/语句 释放该事务锁定的一组资源/数据。该 回滚的会话将观察到Oracle错误:ORA-00060: 等待资源时检测到死锁。甲骨文也将生产 数据库的UDUMP目录下的跟踪文件中的详细信息。
最常见的是这些死锁是由应用程序引起的 涉及同一事务和多个事务中的多表更新 应用程序/事务在同一个表上执行 时间。通过锁定表可以避免这些多表死锁 所有应用程序/事务中的相同顺序,从而阻止a 死锁情况。