必须用ash回溯1–2分钟内enq: tm - contention会话,提取p2值反查子表object_id,再通过dba_constraints与dba_ind_columns确认该子表外键列是否缺失前导列完全匹配的索引。

直接查 v$session 很难定位 enq: TM - contention 的真正源头——因为阻塞者往往不是你正在操作的那张表,而是它的子表,且锁已释放或未被实时捕获。必须用 ASH 回溯采样快照,聚焦 P2 字段反查对象,再验证外键索引缺失。
查ASH中enq: TM - contention会话并提取P2值
等待事件的 P2 是关键:它不是主表 OBJECT_ID,而是被全表扫描校验的子表 OBJECT_ID。不看 P2 就查索引,90% 会走偏。
- 执行范围要窄:只查故障前后 1–2 分钟,避免数据量过大和信号淹没,例如
WHERE sample_time BETWEEN SYSDATE - 2/1440 AND SYSDATE - 过滤必须带
event = 'enq: TM - contention',且加session_state = 'WAITING'或blocking_session IS NOT NULL,否则捞不到真实阻塞链 -
P2值直接用于查dba_objects,别误当成文件号或块号去查dba_data_files或dba_extents
用P2反查子表并确认外键引用关系
查出 P2 对应的表名后,别急着建索引——先确认它是不是主表的子表,以及外键是否真的没索引。很多“子表”其实是独立业务表,跟当前 DML 毫无关系。
- 执行:
SELECT owner, object_name, object_type FROM dba_objects WHERE object_id = &p2_value,若返回的是ORDER_ITEMS、MSG_MSGREC这类明显含“ITEM”“REC”的表,大概率是子表 - 验证外键:查
dba_constraints中constraint_type = 'R'且r_constraint_name指向你主表主键约束的记录 - 重点看这些子表的外键列是否出现在
dba_ind_columns中——已有索引但列顺序不对(如外键是(order_id, line_no),索引却是(line_no, order_id))或前导列缺失(如索引是(status, order_id)),都算无效
建索引必须匹配外键定义,不能重用或凑合
子表外键列上建索引不是“有就行”,Oracle 校验引用时只认前导列完全匹配的索引。建错等于白建,还会增加维护成本。
- 单列外键(如
order_id):索引必须是CREATE INDEX idx_x ON child_table(order_id),不能是(order_id, create_time)——虽然可用,但 Oracle 在某些版本下不会用它加速外键校验 - 复合外键(如
(order_id, line_no)):索引列顺序必须严格一致,且不能跳过前导列;(line_no, order_id)或(order_id, status)都无效 - 子表是分区表?索引必须是
LOCAL,且每个分区都有对应索引段;GLOBAL索引在某些 Oracle 版本中仍会触发全子表扫描
为什么enq: TM - contention常被误判为SQL性能问题
现象上,应用卡在某条 UPDATE 或 DELETE 上,SQL_ID 也明确,很容易让人去优化执行计划或加 hint。但本质是子表无索引导致主表 DML 时被迫申请整张子表的 TM 锁(MODE=4),而此时子表正有未提交事务(哪怕只是 INSERT 一行),就立刻阻塞。
- 这种阻塞与 SQL 执行计划无关,
EXPLAIN PLAN再快也没用 - AWR 中该 SQL 的
elapsed_time高,是因为等锁时间计入了总耗时,不是它自己慢 - 如果多个会话同时对同一主表做 DML,它们都会卡在同一个子表 TM 锁上,形成“扇形阻塞”,此时
blocking_session可能指向不同会话,但P2值一定相同
最易忽略的一点:P2 指向的子表可能本身也有外键指向其他表——也就是说,它既是子表,又是另一张表的父表。这种嵌套外键关系下,要逐层检查所有下游子表的索引,漏一层,问题照旧。











