V$WAIT_CHAINS是Oracle 10gR2起内置的轻量级阻塞链诊断视图,通过CHAIN_ID串联“谁等谁”,BLOCKER_OSID为空者即为阻塞源头;in_wait_secs超60秒需优先处理,enq: TX表明事务未提交,enq: TM多因外键无索引或DDL导致。
用 v$wait_chains 快速抓出阻塞源头
这是 oracle 10gr2 起就内置的最轻量级链式诊断视图,不用拼表、不依赖锁类型判断,chain_id 直接把“谁等谁”串成一条线。执行前确保有 select_catalog_role 权限:
SELECT chain_id, num_waiters, in_wait_secs, osid, blocker_osid,
substr(wait_event_text, 1, 30) AS wait_event
FROM v$wait_chains
WHERE in_wait_secs > 5;关键点:BLOCKER_OSID 为空的那行就是链头(真正卡住别人的会话);in_wait_secs 超过 60 秒就要优先处理;wait_event_text 里出现 enq: TX 是事务未提交,enq: TM 则大概率是 DDL 或外键缺失索引。
用 gv$session + gv$locked_object 定位被锁的表和会话
当需要知道“哪张表被锁了、谁在操作它”,不能只看 v$lock,因为 type='tx' 不代表锁表——真正锁表的是 type='tm' 或通过 gv$locked_object 关联出来的对象。推荐这条兼容 11g–19c 的查询:
SELECT s.sid, s.serial#, s.username, s.machine, s.program,
o.object_name, o.object_type,
DECODE(l.lmode, 2,'row share', 3,'row exclusive', 4,'share', 'other') AS lock_mode,
s.sql_id, q.sql_text
FROM gv$session s
JOIN gv$locked_object l ON s.sid = l.session_id AND s.inst_id = l.inst_id
JOIN dba_objects o ON l.object_id = o.object_id
LEFT JOIN gv$sql q ON s.sql_id = q.sql_id AND s.inst_id = q.inst_id
WHERE s.status = 'ACTIVE';注意:gv$locked_object 只反映当前持有锁的对象,不包含等待方;如果查不到结果但业务明显卡顿,说明锁可能已释放、但阻塞残留还在(比如长事务回滚中),此时要切到 v$wait_chains 或查 v$transaction 的 used_ublk。
避免误判:别把 TX 锁当成表级阻塞源
常见错误是看到 v$lock.type = 'TX' 就杀会话,其实 TX 锁只锁行、不锁表。真正让 INSERT/UPDATE/DELETE 挂起的是 TM 锁(比如 ALTER TABLE 期间其他 DML 被拦住),或外键列没索引导致的隐式全表扫描加锁。
-
TX锁等待事件是enq: TX - row lock contention,根源通常是应用层事务未提交或主键冲突 -
TM锁等待事件是enq: TM - contention,多见于未加索引的外键更新、DDL 执行中、或批量导入时未分批提交 - 查
dba_constraints确认外键是否建了索引:SELECT constraint_name, r_constraint_name FROM dba_constraints WHERE constraint_type = 'R' AND owner = 'YOUR_SCHEMA';
PL/SQL 封装诊断逻辑时,慎用 DBMS_LOCK.SLEEP 模拟等待
写测试脚本复现锁场景时,有人用 DBMS_LOCK.SLEEP(30) 让会话挂起,但这不会触发真实锁等待事件——它只是让 session 睡眠,不占锁资源。真要模拟阻塞,必须走实际 DML:
BEGIN
UPDATE employees SET salary = salary + 1 WHERE employee_id = 100;
-- 不 COMMIT,保持 TX 锁
DBMS_OUTPUT.PUT_LINE('Lock held on EMPLOYEES row 100');
END;另外,PL/SQL 块内执行 DML 后若未显式 COMMIT 或 ROLLBACK,事务会一直持锁直到会话断开,这点比 SQL*Plus 更容易忽略——因为 PL/SQL 块执行完并不自动结束事务。
最常被跳过的动作是查 v$transaction.start_time 和 used_ublk:长时间未提交的事务,哪怕没在 v$session.event 里显示等待,也可能正卡在回滚阶段,这时候 used_ublk 值越大,说明回滚越慢、影响越广。


















