需要帮助理解SELECT ... FOR UPDATE导致死锁的行为

时间:2013-12-16 12:24:47

标签: oracle jpa deadlock

我有两个并发事务执行这段代码(为简化说明而简化):

@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的提交。但是,这段代码是死锁的。我哪里错了?

2 个答案:

答案 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   死锁情况。