现象极具迷惑性:应用"卡住不动",数据库 CPU 很闲、IO 很闲、AWR Top 1 却是
enq: TX - row lock contention。这是应用并发设计问题在数据库里的投影。本篇给出阻塞链三查、死锁处理流程,以及 KILL 会话的正确姿势。
一、30 秒原理课:Oracle 怎么锁行
- DML 修改某行 → 在行所在数据块记录事务信息(ITL 槽)→ 其他会话想改同一行,就要等第一个事务
COMMIT/ROLLBACK; - 这个等待就是
enq: TX - row lock contention; - Oracle 行锁没有锁管理器——锁信息随数据块走,所以行锁永远不会"锁表撑爆内存";
- 另一个产生 TX 等待的场景:唯一键冲突(插入撞车时也表现为 TX 等待);
- 表级的是
enq: TM - contention:典型如 DDL 撞上未提交的 DML、或子表外键无索引时的级联操作。
二、阻塞链三查(标准排查 SQL)
2.1 第一查:谁阻塞了谁
SELECTsid,serial#, username, event, sql_id, sql_child_number,blocking_session,blocking_session_status,seconds_in_wait,final_blocking_sessionFROMv$sessionWHEREblocking_sessionISNOTNULL;输出直接给出"受害者 sid → 加害者 blocking_session"的映射,多条记录串起来就是阻塞链。
2.2 第二查:加害者在干什么
-- 用阻塞链顶端的 sid 查它的 SQLSELECTsql_fulltextFROMv$sqlWHEREsql_id=(SELECTsql_idFROMv$sessionWHEREsid=&blocker_sid);-- 它的事务开了多久、锁了哪些对象SELECTs.sid,s.serial#, s.username, t.start_time,ROUND(t.used_ublk*8/1024)undo_mb,s.row_wait_obj#FROMv$sessions,v$transactiontWHEREs.taddr=t.addrANDs.sid=&blocker_sid;重点看start_time——事务挂了 3 小时没提交,多半是应用忘了 commit 或连接被挂起。
2.3 第三查:锁的是什么对象
-- 行级锁对象(把 sid 换成受害者的)SELECTo.owner,o.object_name,o.object_typeFROMdba_objects o,v$locked_object lWHEREo.object_id=l.object_id;-- 受害者正在等哪一行的数据SELECTs.sid,o.object_name,s.row_wait_file#, s.row_wait_block#, s.row_wait_row#FROMv$sessions,dba_objects oWHEREs.row_wait_obj# = o.object_id AND s.sid = &victim_sid;三、处理:KILL 的正确姿势
确认加害者是"僵死事务"(应用崩溃残留、忘提交的僵尸连接)后:
ALTERSYSTEMKILLSESSION'sid,serial#'IMMEDIATE;⚠️三个纪律:
- 别一上来就 KILL:如果加害者是正常长事务(跑批),KILL 它等于制造更大的故障。先确认业务归属;
- 分布式事务要谨慎:盲 KILL 可能产生 in-doubt 事务,需要 DBA 手动
COMMIT/ROLLBACK FORCE收尾;- 会话被标记
KILLED却不消失(OS 层还活着)时,才需要 OS 层配合:
# 找到对应 server process(只列流程,实际操作需确认)SELECT p.spid, s.sid, s.program FROMv$processp,v$sessions WHERE p.addr=s.paddr;kill-9<spid># PMON 会接管并回滚其事务四、死锁 ORA-00060:Oracle 已经替你处理了一半
死锁 = 事务 A 锁行 1 等行 2,事务 B 锁行 2 等行 1,谁也不让谁。Oracle 检测到后会自动回滚其中一个事务(不是 KILL 会话),应用收到ORA-00060: deadlock detected while waiting for resource。
4.1 DBA 要做什么
死锁是应用设计问题,DBA 的职责是提供"破案材料":
- alert.log 里定位
ORA-00060时间点; - 打开对应的trace 文件(alert.log 中
DEADLOCK DETECTED附近会给出路径)——里面有完整的两张死锁图:各自持有的行、等待的行、当前 SQL、user/OS 信息; - 把材料交给应用:通常是两个事务以相反顺序更新同样的两批行(或唯一键插入并发、位图索引并发 DML)。
4.2 常见修复(应用侧)
- 统一更新顺序:所有事务按相同顺序(如按主键排序)访问行;
- 缩小事务范围:先查询后更新的事务,把"查"移出事务;
- 并发插入唯一键:改用序列 + 异常捕获重试;
- 外键无索引引发的 TM/死锁:给外键列建索引。
五、和"锁"容易混淆的等待
| 等待 | 本质 | 处理方向 |
|---|---|---|
enq: TX - row lock contention | 等另一个事务提交(行锁/ITL/唯一键) | 本篇 |
enq: TX - allocate ITL entry | 块内事务槽不足 | 提高initrans、重建段 |
enq: TM - contention | 表级:DDL 撞 DML、外键无索引 | 加索引、错峰 DDL |
buffer busy waits | 等块上的 IO/构造,不是事务锁 | 热块打散、反向键索引 |
library cache lock/pin | 等 DDL/编译锁 | 查对象级 DDL 冲突、硬解析 |
六、防重于治:三个长期措施
- 应用侧连接池超时 + 事务心跳:僵尸连接是阻塞链的最大来源;
- 外键一律建索引(TM 争用与死锁的经典来源);
- 监控固化:把"阻塞链第一查"做成巡检脚本,阻塞超 5 分钟告警:
-- 巡检告警样例SELECTcount(*)FROMv$sessionWHEREblocking_sessionISNOTNULLANDseconds_in_wait>300;七、小结
enq: TX高 = 应用并发冲突,数据库只是"现场";- 三查:
blocking_session阻塞链 → 加害者 SQL/事务时长 →v$locked_object对象定位; - KILL 前确认业务归属与分布式事务,标记 KILLED 不消失再考虑 OS 层;
- ORA-00060 由 Oracle 自动化解,DBA 的任务是拿 trace 给应用修顺序;
- 外键索引、连接池治理是长期解。
下一篇:10-慢SQL分析路径 —— 性能篇收官:单条 SQL 的完整分析路径。