ORA-60死锁是Oracle数据库中事务并发场景下典型的问题,两个或多个事务互相持有对方需要的锁资源,且都不释放自身持有的锁,就会导致死锁产生,数据库会自动回滚其中一个事务并抛出ORA-60错误。下面我们通过实际实验来复现该错误。

实验环境准备
首先需要一个可用的Oracle数据库实例,建议使用11g及以上版本,准备一个测试表并插入初始数据,执行以下SQL完成准备:
-- 创建测试表
CREATE TABLE deadlock_test (
id NUMBER PRIMARY KEY,
name VARCHAR2(50)
);
-- 插入测试数据
INSERT INTO deadlock_test VALUES (1, '记录1');
INSERT INTO deadlock_test VALUES (2, '记录2');
-- 提交事务
COMMIT;死锁复现步骤
我们需要开启两个独立的数据库会话,模拟两个并发事务的操作,步骤如下:
会话1操作
第一个会话先更新id为1的记录,此时会话1会持有id=1这一行的排他锁:
-- 会话1执行,更新id=1的记录,不提交 UPDATE deadlock_test SET name = '会话1修改记录1' WHERE id = 1;
会话2操作
第二个会话先更新id为2的记录,此时会话2会持有id=2这一行的排他锁:
-- 会话2执行,更新id=2的记录,不提交 UPDATE deadlock_test SET name = '会话2修改记录2' WHERE id = 2;
触发死锁
接下来两个会话互相等待对方释放锁,先让会话1尝试更新id=2的记录,此时会话1会阻塞,等待会话2释放id=2的锁:
-- 会话1执行,尝试更新id=2的记录,此时会阻塞 UPDATE deadlock_test SET name = '会话1修改记录2' WHERE id = 2;
再让会话2尝试更新id=1的记录,此时会话2需要等待会话1释放id=1的锁,而会话1还在等待会话2释放id=2的锁,形成死锁循环:
-- 会话2执行,尝试更新id=1的记录,触发死锁 UPDATE deadlock_test SET name = '会话2修改记录1' WHERE id = 1;
执行上述会话2的语句后,Oracle会检测到死锁,自动回滚会话2的事务,抛出如下错误:
ORA-00060: deadlock detected while waiting for resource
查看锁信息验证
死锁发生后,我们可以查询v$lock数据字典视图,查看当时的锁持有情况,验证死锁的产生逻辑:
-- 查询锁信息,需要DBA权限
SELECT
s.sid,
s.serial#,
l.type,
l.id1,
l.id2,
l.lmode,
l.request
FROM
v$lock l
JOIN
v$session s ON l.sid = s.sid
WHERE
l.type IN ('TM', 'TX')
ORDER BY
s.sid;查询结果中,lmode表示当前持有的锁模式,request表示请求的锁模式,当存在两个会话互相持有对方请求的锁时,就能对应死锁的场景。
避免ORA-60死锁的方法
结合实际实验过程,我们可以总结出几个避免ORA-60死锁的实用方法:
- 所有事务按照相同的顺序访问数据库对象,比如都先操作id小的记录,再操作id大的记录,避免循环等待
- 尽量缩短事务的执行时间,减少锁的持有时长,降低死锁发生的概率
- 合理设置事务的隔离级别,避免不必要的锁升级
- 在应用层捕获ORA-60错误,进行重试操作,减少对业务的影响
通过本次实验,我们可以清晰看到ORA-60死锁的完整触发过程,也能更理解事务并发场景下锁资源的竞争逻辑,在实际开发中可以有针对性地规避这类问题。