绝大多数enq: TM - contention等待事件根源是外键列缺索引;通过ASH查p2得被阻塞子表object_id,再结合dba_constraints与dba_ind_columns定位缺失索引的外键列,且索引须为B-Tree、可见、字段顺序一致并覆盖全部分区。
绝大多数 enq: tm - contention 等待事件,根源就是外键列上缺索引——不是“可能”,而是“大概率就是它”。
怎么快速定位是哪个外键没建索引
别猜,直接查 v$active_session_history 或历史 dba_hist_active_sess_history:
- 过滤
event = 'enq: TM - contention' - 重点看
p2字段值:它就是被阻塞对象的object_id,不是文件号、不是块号 - 用
dba_objects反查这个object_id对应哪张表——大概率是你没动的那张“子表” - 比如你正在
DELETE主表ORDERS,但p2指向的是ORDER_ITEMS,那就盯住它
确认外键关系并检查索引是否存在
查出子表后,验证它是否真通过外键引用了你操作的主表:
- 执行:
SELECT constraint_name, r_constraint_name FROM dba_constraints WHERE table_name = 'ORDER_ITEMS' AND constraint_type = 'R' - 拿到
r_constraint_name后,再去查主表的主键名:SELECT constraint_name FROM dba_constraints WHERE table_name = 'ORDERS' AND constraint_type = 'P' - 确认匹配后,检查子表外键列上有没有索引:
SELECT index_name FROM dba_ind_columns WHERE table_name = 'ORDER_ITEMS' AND column_name IN ('ORDER_ID') - 注意:如果子表是分区表,索引必须是
LOCAL且覆盖全部分区;GLOBAL索引或只建在部分分区上的索引,仍会触发全表扫描和锁争用
建索引时要注意的硬性条件
不是随便建个索引就管用,必须满足以下三点:
- 索引字段顺序要和外键定义一致,比如外键是
(order_id, line_no),索引也得是(order_id, line_no),反过来或只建(line_no)都无效 - 索引不能是函数索引、位图索引或不可见索引(
INVISIBLE)——Oracle 在外键校验时只认普通 B-Tree 可见索引 - 如果外键允许
NULL,索引仍需包含该列;Oracle 不会跳过 NULL 值去跳过校验逻辑 - 建完立刻生效,不需要重启或刷新缓存,但已有阻塞会话不会自动释放,得等它们超时或被 kill
为什么其他原因很少见,而外键索引缺失最常踩坑
在 Oracle 19c 中,并行 DML、LOCK TABLE、RAC 的 GC 问题确实也能引发 enq: TM - contention,但它们有明显特征:
- 并行 DML 通常伴随
px进程、PDML相关等待,且 SQL 执行计划里带PX字样 -
LOCK TABLE是显式语句,应用日志或审计里能直接看到 - RAC 场景下,
enq: TM - contention往往跨节点出现,且常伴生gc *类等待 - 而外键无索引导致的问题,现象高度统一:主表 DML 卡住、
p2指向子表、子表上有未提交事务、AWR 中该等待占比突然飙升到 70% 以上
真正容易被忽略的是:即使外键加了 ON DELETE CASCADE,只要没索引,删主表一行,照样会全表扫描子表并申请整表 TM 锁(mode=4),把所有子表 DML 全堵死。











