Oracle TM 锁 Mode 4(Share)问题排查总结

Oracle TM 锁 Mode 4(Share)问题排查总结

一、概述

Oracle的**TM锁(表级锁,Table Lock)**是保障DML操作与DDL操作隔离的核心机制,是一种表级锁,保护表结构,在DML操作期间获取。

TM锁模式

模式 名称 缩写 典型操作
1 Null N 某些内部操作
2 Row Share RS/SS SELECT ... FOR UPDATE
3 Row Exclusive RX/SX INSERT / UPDATE / DELETE
4 Share S CREATE INDEX ONLINE、外键未建索引时对父表 DML
5 Share Row Exclusive SRX/SSX 某些 DDL
6 Exclusive X LOCK TABLE ... IN EXCLUSIVE MODE、DDL

二、TM Mode 4模式的操作总结

显式执行 LOCK TABLE 命令(最常见)

DBA或应用主动执行了表级锁定语句:

复制代码
LOCK TABLE your_table IN SHARE MODE;

这通常用于保证在批量处理期间表结构不变,或为了配合外键做级联操作前的准备。

传统导出工具(exp)的 CONSISTENT=Y

早年的Oracle exp 工具,如果加上 CONSISTENT=Y 参数,为保证导出数据在时间点上的全局一致性,会对导出的表施加 Mode=4 S锁,防止导出过程中数据发生变更。(备注:新版 expdp(数据泵)默认使用Mode=5,行为有所不同)

物化视图(Materialized View)完全刷新

当执行 REFRESH COMPLETE 且采用**原子刷新(ATOMIC_REFRESH=>TRUE)**时,在刷新阶段,Oracle可能会对基表持有Mode=4 S锁,以防止刷新期间基表数据被修改,确保快照一致性。

外键未索引导致的"隐形"S锁(生产中极高的踩坑点

这是大多数生产环境意外出现Mode=4锁的根本原因

如果子表(外键表)的外键列上没有索引 ,当父表的主键/唯一键发生 UPDATEDELETE 时,Oracle为了维护参照完整性(检查子表是否有对应记录),会在子表上自动施加一个 Mode=4 S锁(而不是行级锁),导致整个子表的DML被阻塞。

举例ORDERS表是子表(有CUSTOMER_ID外键)。如果CUSTOMERS表更新了ID=1的主键值,而ORDERS.CUSTOMER_ID没有索引,ORDERS整张表会被加S锁,此时任何其他会话都无法向ORDERS插入新订单。

分区维护操作(如 EXCHANGE PARTITION

在执行 ALTER TABLE ... EXCHANGE PARTITION 交换分区时,Oracle会对涉及的表短暂持有S锁,完成元数据切换。

三、外键未索引导致的TM 锁 Mode 4案例

当子表的外键列没有建索引 时,对父表的 UPDATE 或 DELETE 操作需要在子表上获取 mode 4(Share 锁),目的是防止在检查外键约束期间子表被并发修改。

1、环境准备

复制代码
-- 1. 创建父表和子表
CREATE TABLE parent (
  id NUMBER PRIMARY KEY,
  name VARCHAR2(50)
);

CREATE TABLE child (
  id NUMBER PRIMARY KEY,
  parent_id NUMBER,
  data VARCHAR2(50),
  CONSTRAINT fk_child_parent FOREIGN KEY (parent_id) REFERENCES parent(id)
  -- 注意:parent_id 列上没有建索引!
);

-- 2. 插入测试数据
INSERT INTO parent VALUES (1, 'PARENT_1');
INSERT INTO parent VALUES (2, 'PARENT_2');
INSERT INTO child VALUES (101, 1, 'CHILD_1');
INSERT INTO child VALUES (102, 1, 'CHILD_2');
INSERT INTO child VALUES (103, 2, 'CHILD_3');
COMMIT;


-- 会话 A
UPDATE child SET data = 'MODIFIED' WHERE id = 101;
-- 未提交,持有 child 表的 TM 锁 mode=3(Row Exclusive)

-- 会话 B
DELETE FROM parent WHERE id = 1;
-- 需要检查子表的外键约束
-- 需要在 child 表上获取 TM 锁 mode=4(Share)
-- 但会话 A 持有 mode=3,与 mode=4 互斥
-- → 会话 B 被阻塞,等待 mode 4

2、查询分析

复制代码
1)通过gv$lock视图查询,检查数据中是否存在锁等待
SESS       LMODE                CTIME    INST_ID        ID1        ID2    REQUEST TY
---------- --------------- ---------- ---------- ---------- ---------- ---------- --
Holder: 64 row exclusive           42          1      87488          0          0 TM
Waiter: 62 none                    22          1      87488          0          4 TM
--可以看到TYPE为TM表锁,request=4,即mode=4请求模式!

2)、通过ID1,查询具体的表对象(表锁TM模式下,ID1表示对象id)
col owner format a10
col object_name format a15
col object_type format a15
select owner,object_name,object_type from dba_objects a where object_id=87488;
OWNER      OBJECT_NAME     OBJECT_TYPE
---------- --------------- ---------------
USER1      CHILD           TABLE


3)查询具体的TX锁等待会话,等待会话正在执行的语句,正在等待的对象,会话自身的连接属性等详细信息(建议使用连接工具查询,而非sqlplus方式)
SQL_ID        EVENT   USERNAME  OWNER   OBJECT_NAME  OBJECT_TYPE    SID BLOCKING_INSTANCE BLOCKING_SESSION
3n86yupq0t0ca enq: TM - contention USER1 USER1 CHILD  TABLE 62      1  64
--可以堵塞sid为64,

3、解决方案

在外键列上创建索引,创建后,父表DML对子表仅加行级锁(Mode=3),不再锁整表(mode=4)

四、查询数据库中哪些外键未创建索引

复制代码
set linesize 200 pagesize 999 
col owner format a20
col table_Name format a20
col FK_COLUMNS format a20

WITH fk_cols AS (
    -- 步骤1:聚合所有外键的列(按顺序拼接)
    SELECT
        c.owner,
        c.table_name,
        c.constraint_name AS fk_name,
        LISTAGG(cc.column_name, ',' ) WITHIN GROUP (ORDER BY cc.position) AS fk_columns
    FROM
        dba_constraints c
        JOIN dba_cons_columns cc ON c.owner = cc.owner AND c.constraint_name = cc.constraint_name
    WHERE
        c.constraint_type = 'R'   -- R 代表外键
        -- 排除 Oracle 系统内置用户
        AND c.owner NOT IN (
            'SYS','SYSTEM','DBSNMP','SYSMAN','OUTLN','MDSYS',
      'ORDSYS','EXFSYS','DMSYS','WMSYS','CTXSYS','ANONYMOUS',
      'XDB','ORDPLUGINS','OLAPSYS','APPQOSSYS','APEX_030200',
      'APEX_040000','APEX_040200','APEX_050000','APEX_PUBLIC_USER',
      'FLOWS_FILES','OWBSYS','OWBSYS_AUDIT','ORDDATA','LBACSYS',
      'OJVMSYS','GSMADMIN_INTERNAL','MDDATA','SPATIAL_WFS_ADMIN_USR',
      'SPATIAL_CSW_ADMIN_USR','DIP','XS$NULL','SI_INFORMTN_SCHEMA'
        )
    GROUP BY
        c.owner, c.table_name, c.constraint_name
),
idx_cols AS (
    -- 步骤2:聚合所有索引的列(按顺序拼接)
    SELECT
        ic.table_owner,
        ic.table_name,
        ic.index_name,
        LISTAGG(ic.column_name, ',' ) WITHIN GROUP (ORDER BY ic.column_position) AS idx_columns
    FROM
        dba_ind_columns ic
    GROUP BY
        ic.table_owner, ic.table_name, ic.index_name
)
-- 步骤3:筛选出外键列不是任何索引的"前导列组合"的情况
SELECT
    f.owner ,
    f.table_name ,
    f.fk_name ,
    f.fk_columns
FROM
    fk_cols f
WHERE
    NOT EXISTS (
        SELECT 1
        FROM idx_cols i
        WHERE
            i.table_owner = f.owner
            AND i.table_name = f.table_name
            --匹配逻辑:索引列必须以外键列开头(完全匹配 或 外键列 + 其他列)
            AND ( i.idx_columns = f.fk_columns
                  OR i.idx_columns LIKE f.fk_columns || ',%' )
    )
ORDER BY
    f.owner, f.table_name;
相关推荐
L-岁月染过的梦26 分钟前
把常用开发小工具收进 IDEA:Develop Helper 插件介绍
java·数据库·intellij-idea
IT码农-爱吃辣条28 分钟前
Milvus 向量数据库 Python 实战教程
数据库·python·milvus
专注API从业者1 小时前
Open‑Claw 实战|无需逆向,快速搭建电商商品监控与数据分析系统
开发语言·数据结构·数据库·数据分析·php
2301_800074211 小时前
MySQL部分语句
数据库·mysql
CodeStats1 小时前
MySQL与Oracle数据类型底层设计原理对比(下)
数据库·mysql·oracle
疯狂打码的少年1 小时前
【数据库技术】SQL高级查询(分组/排序/聚合函数)
java·数据库·笔记·sql
IvorySQL1 小时前
PostgreSQL 日报|备用服务器 FSM 数据不一致(9 月 2 日)
服务器·数据库·postgresql
zcmodeltech2 小时前
风力发电沙盘模型控制系统设计与灯光联动实现方案——基于STM32与Modbus RTU,服务范围覆盖全国的多场景风力发电沙盘模型定制
网络·数据库·stm32·单片机·嵌入式硬件·能源
程序员黎剑2 小时前
MySQL-JOIN优化-NLJ与BNL的区别与实战
数据库·mysql