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

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

一、概述

上一篇文章《Oracle 行锁问题排查总结(enq: TX - row lock contention)》,enq: TX - row lock contention 锁等待事件中,P1 参数值将请求锁的名称和请求模式编码在一起,可以通过查看 P1RAW 的十六进制值来解析(通过公式 name<<16 | mode 计算),或者通过 SQL 函数转换,具体如下

  • name (锁名称) :标识锁的类型。对于行锁,其名称固定为 TXTX 的 固定ASCII 码是 0x5458

  • mode (锁模式):标识请求的锁模式。

    6 (排他锁, Exclusive):这是最常见的模式,表示一个会话持有行级锁,另一个会话正在等待该锁释放(例如,两个会话同时更新或删除同一条记录)

    4 (共享锁, Share):较少见,通常与唯一键冲突(Unique Key Constraint)或位图索引(Bitmap Index)的并发更新有关。

观察 p1 的十六进制表示 (p1raw),可以直接看到锁名称和模式。

  • p1raw = 54580006:后四位 0006 表示模式为 6 (排他锁),最常见的行锁等待。
  • p1raw = 54580004:后四位 0004 表示模式为 4 (共享锁)

二、常见原因

Oracle数据库中引起TX 锁 Mode 4的锁等待,主要分为三类:

1、ITL 事务槽竞争

2、唯一索引 / 主键 (并发插入相同键值)

3、位图索引 (并发修改同一键值)

4、CREATE INDEX ONLINE

原理分析

1、ITL 事务槽竞争

每个数据块头部有一个事务槽表(ITL,Interested Transaction List) ,用于记录哪些事务正在修改这个块。当块中所有 ITL 槽位都被占用,且块内没有足够空间动态扩展新槽位时,新事务必须等待某个已有事务提交或回滚释放槽位。此时新事务的 TX 锁请求模式为 mode 4

2、唯一索引 / 主键 (并发插入相同键值)

当两个会话并发向唯一索引(包括主键)插入相同的键值 时,第一个会话获取 TX mode 6(Exclusive),第二个会话在索引块中检测到键值冲突,必须等待第一个会话提交或回滚来确认该键值是否最终存在。此时第二个会话的 TX 锁请求模式为 mode 4

3、位图索引 (并发修改同一键值)

位图索引中,同一个键值的所有行的位置信息存储在同一个位图段 中。当多个会话并发修改(INSERT/UPDATE/DELETE)包含同一键值的行时,后到的会话必须等待先到的会话释放位图段,此时 TX 锁请求模式为 mode 4

4、CREATE INDEX ONLINE

CREATE INDEX ONLINE 在创建索引期间需要对表数据行持有 TX 锁 mode 4,在此期间允许并发 DML,但如果表上已有未提交的 DML 事务,创建索引的操作会被阻塞。

三、ITL事务槽概述

ITL(Interested Transaction List,感兴趣事务列表),可以把ITL理解为位于每个Oracle数据块头部的一组"停车位"或"槽位"。

  • 作用 :任何一个事务(如UPDATEDELETE)在修改数据块中的行之前,都必须先在块头部的ITL中占用一个槽位。这个槽位会记录该事务的ID、回滚段地址等关键信息。
  • 记录内容 :每个ITL槽位主要记录事务ID (XID)回滚段地址 (UBA)事务状态 (活跃/已提交)以及锁定的行数 (Lck)
  • 复用机制 :当一个事务提交(COMMIT)或回滚(ROLLBACK)后,它占用的ITL槽位就会被释放,供其他新事务复用。

Oracle的ITL数量是动态的,其自动扩展遵循以下逻辑:

  1. 初始分配 :数据块在创建时,会根据对象的 INITRANS 参数预先分配一定数量的ITL槽位。
  2. 按需扩展 :当并发事务数超过了当前已有的ITL槽位数时,Oracle会尝试在数据块头部动态地创建一个新的ITL槽位
  3. 扩展条件 :这个动态扩展不是无限制的 ,必须同时满足两个条件:
    • 有足够的空闲空间 :数据块的 PCTFREE 区域中必须有足够的剩余空间来容纳新的ITL槽位(每个槽位约占用 24字节或46字节)。
    • 未达到最大限制 :ITL槽位的总数不能超过其硬性上限。在 Oracle 10g 及以后版本,MAXTRANS 参数已被废弃,其功能由Oracle自动管理,最大并发事务数被硬性限制为 255 。实际上,这个上限还会受数据块大小影响,例如对于8KB的块,实际最大ITL数约为169

Oracle数据库中ITL事务槽等待的名称为: enq: TX - allocate ITL entry

四、ITL 事务槽竞争,引起的锁等待实验、分析和解决方案

1、实验验证

复制代码
-- 0. 先删除测试表
drop table itl_test purge;
-- 1. 创建一个 INITRANS=1 且 MAXTRANS=2 的表(故意限制事务槽数量)
CREATE TABLE itl_test (
  id   NUMBER,
  name char(2000)
) 
INITRANS 1  PCTFREE 0;  
-- PCTFREE=0 使块内几乎没有空间扩展 ITL
-- MAXTRANS 2参数已经废弃

-- 2. 插入刚好填满一个数据块的数据
BEGIN
  FOR i IN 1..6 LOOP
    INSERT INTO itl_test VALUES (i, RPAD('A', 1500, 'B'));
  END LOOP;
  COMMIT;
END;
/

-- 3. 查看该数据块所在的文件号和块号
SELECT id,dbms_rowid.rowid_relative_fno(rowid) AS file#,
       dbms_rowid.rowid_block_number(rowid) AS block#
FROM itl_test;
        ID      FILE#     BLOCK#
---------- ---------- ----------
         1          4        187
         2          4        187
         3          4        187
         4          4        187   --可以看到187块,已经插入4条数据,且数据块已满(因为Oracle使用191插入新行数据了)
         5          4        191
         6          4        191
         
-- 会话 A
UPDATE itl_test SET name = 'A' WHERE id = 1;
-- 未提交,占用块的 ITL 槽位 1


-- 会话 B
UPDATE itl_test SET name = 'B' WHERE id = 2;
-- 未提交,占用块的 ITL 槽位 2
-- 此时 MAXTRANS=2,两个槽位已满


-- 会话 C
UPDATE itl_test SET name = 'C' WHERE id = 3;
-- 未提交,占用块的 ITL 槽位 3



-- 会话 D   ---->发生ITL 事务槽竞争锁等待
UPDATE itl_test SET name = 'D' WHERE id = 4;
-- 块中 ITL 槽位占用3个
-- 且 PCTFREE=0 无法扩展新槽位
-- 块中已经没有任何的空闲空间,无法扩展ITL槽位,但是会话D需要请求ITL槽进行事务,因此发生ITL 事务槽竞争等待!
-- 会话 D 的 TX 锁请求 mode=4,等待会话 A 或 B 释放槽位

2、查询分析

复制代码
1)通过gv$lock视图查询,检查数据中是否存在锁等待
SESS       LMODE                CTIME    INST_ID        ID1        ID2    REQUEST TY
---------- --------------- ---------- ---------- ---------- ---------- ---------- --
Holder: 64 exclusive              130          1     983055         65          0 TX
Waiter: 60 none                     0          1     983055         65          4 TX
--可以看到REQUEST=4,请求MODE=4的锁,发生了等待

2)查询具体的TX锁等待会话,等待会话正在执行的语句,正在等待的对象,会话自身的连接属性等详细信息(建议使用连接工具查询,而非sqlplus方式)
SQL_ID        EVENT                          USERNAME   OWNER      OBJECT_NAME     OBJECT_TYPE       SID BLOCKING_INSTANCE BLOCKING_SESSION
------------- ------------------------------ ---------- ---------- --------------- --------------- ----- ----------------- ----------------
35sbz2q7njdt6 enq: TX - allocate ITL entry   USER1      USER1      ITL_TEST        TABLE              60                 1    64
--可以看到,事务槽等待的名称为enq: TX - allocate ITL entry

3)查询等待的对象和具体的行数据
set linesize 200 pagesize 999
col owner for a15
col object_name for a20
select inst_id,sid,row_wait_obj#, row_wait_file#, row_wait_block#, row_wait_row#,b.owner,b.object_name
from gv$session a,dba_objects b
where a.event like 'enq: TX%'
and a.row_wait_obj# = b.object_id(+);

INST_ID   SID ROW_WAIT_OBJ# ROW_WAIT_FILE# ROW_WAIT_BLOCK# ROW_WAIT_ROW# OWNER           OBJECT_NAME
------- ----- ------------- -------------- --------------- ------------- --------------- -------------
      1    60         87480              4             187             0 USER1           ITL_TEST
--可以看到ROW_WAIT_BLOCK#=187,与我们实验开始前,查询的块号一致!

3、解决方案

**根本原因分析:**DML操作并发性高 + 数据块已满(无空闲空间)+ INITRANS初始事务槽配置数据低

方案1:调整段参数(PCTFREE 和 INITRANS)

调整段参数(修改 INITRANS 和 PCTFREE )只影响修改后新分配的数据块,原有已存在的数据块(即当前引发等待的"热块")参数不会改变

方案2:物理重构数据(使参数更改生效)

对表或索引进行物理重组,才能让所有数据块(尤其是热点块)应用新参数

方案3:分散热点块(分区)

例如HASH分区:将表改为 HASH分区,利用分区键的散列值将高并发插入/更新的数据分散到多个物理数据块上,从根本上降低单个块的并发压力。

方案4:优化应用程序事务

缩短事务与批量提交

查询数据库中哪些对象发生了 ITL 等待

复制代码
-- 查看当前哪些对象发生了 ITL 等待
SELECT owner, object_name, object_type, value 
FROM v$segment_statistics 
WHERE statistic_name = 'ITL waits' AND value > 0;

五、唯一索引 / 主键 (并发插入相同键值),引起的锁等待实验、分析和解决方案

1、实验验证

复制代码
-- 1. 创建带唯一约束的表
CREATE TABLE unique_test (
  id   NUMBER PRIMARY KEY,
  data VARCHAR2(50)
);

INSERT INTO unique_test VALUES (1, 'EXISTING');
COMMIT;

-- 会话 A
INSERT INTO unique_test VALUES (100, 'SESSION_A');
-- 未提交,持有 TX mode 6

-- 会话 B
INSERT INTO unique_test VALUES (100, 'SESSION_B');
-- 检测到唯一键冲突
-- 必须等待会话 A 提交或回滚
-- TX 锁请求 mode=4

2、查询分析

复制代码
1)通过gv$lock视图查询,检查数据中是否存在锁等待
SESS       LMODE                CTIME INST_ID        ID1        ID2    REQUEST TY
---------- --------------- ---------- ------- ---------- ---------- ---------- --
Holder: 64 exclusive               48       1    1114124         67          0 TX
Waiter: 62 none                    16       1    1114124         67          4 TX
--可以看到REQUEST=4,请求MODE=4的锁,发生了等待

2)查询具体的TX锁等待会话,等待会话正在执行的语句,正在等待的对象,会话自身的连接属性等详细信息(建议使用连接工具查询,而非sqlplus方式)
2026-08-10 14:01 b6hjj0s0qk5d6 enq: TX - row lock contention  USER1  oracle  19941         host40rac1
sqlplus@host40rac1 (TNS V1-V3) SQL*Plus    INSERT INTO unique_test VALUES (100, 'SESSION_B')
2026-08-10/15:01:10 257    62     0   1   64 2527830 1415053316    1114124         67 kill -9 19951
            
--可以看到,等待的名称为enq: TX - row lock contention,等待的语句符合实验,阻塞的会话sid为64

3)查询等待的对象和具体的行数据  ---可以看到,这种锁等待,无法定位具体的数据行
set linesize 200 pagesize 999
col owner for a15
col object_name for a20
select inst_id,sid,row_wait_obj#, row_wait_file#, row_wait_block#, row_wait_row#,b.owner,b.object_name
from gv$session a,dba_objects b
where a.event like 'enq: TX%'
and a.row_wait_obj# = b.object_id(+);

INST_ID   SID ROW_WAIT_OBJ# ROW_WAIT_FILE# ROW_WAIT_BLOCK# ROW_WAIT_ROW# OWNER           OBJECT_NAME
------- ----- ------------- -------------- --------------- ------------- --------------- --------------
      1    62            -1              0               0             0
---可以看到,这种锁等待,无法定位具体的数据行

3、解决方案

**根本原因分析:**会话A先插入了键值 V=100(未提交),会话B也尝试插入 V=100。Oracle为了保证唯一性,必须等待会话A提交(报错ORA-00001)或回滚(B成功插入)。在此期间,会话B会一直处于行锁等待状态。

必须从应用逻辑SQL写法来解决!

1、 调整主键生成策略, 排查为何多个会话会生成相同的主键值

2、使用 IGNORE_ROW_ON_DUPKEY_INDEX Hint

如果业务允许"重复键则忽略(跳过)",这是Oracle针对此场景的特别提示。该Hint会在探测到唯一键冲突时,立即返回(不等待锁释放),不会产生行锁等待,且性能极佳。

复制代码
INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(table_name unique_index_name) */ 
INTO table_name (id, col1) VALUES (100, 'test');

适用场景:ETL数据同步、日志插入、批量拉取数据(重复即丢弃)。

3、应用层去重与幂等,由应用层来确保数据的唯一性!

六、位图索引 (并发修改同一键值),引起的锁等待实验、分析和解决方案

1、实验验证

复制代码
-- 1. 创建表并建位图索引
CREATE TABLE bitmap_test (
  id     NUMBER PRIMARY KEY,
  status VARCHAR2(20),
  data   VARCHAR2(50)
);

CREATE BITMAP INDEX idx_bitmap_status ON bitmap_test(status);

-- 2. 插入测试数据
INSERT INTO bitmap_test VALUES (1, 'ACTIVE', 'DATA_1');
INSERT INTO bitmap_test VALUES (2, 'ACTIVE', 'DATA_2');
INSERT INTO bitmap_test VALUES (3, 'INACTIVE', 'DATA_3');
COMMIT;


-- 会话 A
UPDATE bitmap_test SET status = 'INACTIVE' WHERE id = 1;
-- 修改了 status='ACTIVE' 的行
-- 位图索引中 'ACTIVE' 键值的位图段被锁定
-- 未提交

-- 会话 B
UPDATE bitmap_test SET status = 'UNKOWN' WHERE id = 2;
-- 同样修改了 status='ACTIVE' 的行
-- 需要修改同一个位图段 → TX 锁请求 mode=4
-- 被会话 A 阻塞

注意:只有修改涉及到bitmap索引列的时候,才会出现锁等待,修改不涉及位图索引列,不会产生位图锁!

2、查询分析

复制代码
1)通过gv$lock视图查询,检查数据中是否存在锁等待
SESS       LMODE                CTIME    INST_ID        ID1        ID2    REQUEST TY
---------- --------------- ---------- ---------- ---------- ---------- ---------- --
Holder: 62 exclusive               63          1     851993         70          0 TX
Waiter: 63 none                    57          1     851993         70          4 TX
--可以看到REQUEST=4,请求MODE=4的锁,发生了等待

2)查询具体的TX锁等待会话,等待会话正在执行的语句,正在等待的对象,会话自身的连接属性等详细信息(建议使用连接工具查询,而非sqlplus方式)
2026-08-10 14:01 04thw0577tc2g enq: TX - row lock contention  USER1  oracle  19954
host40rac1   sqlplus@host40rac1 (TNS V1-V3) SQL*Plus UPDATE bitmap_test SET status = 'UNKOWN' WHERE id = 2
2026-08-10/15:28:33  10-AUG-26 2026-08-10 15:31:05 USER1           IDX_BITMAP_STATUS             153    63     0                  1               62 INDEX  2379702 1415053316     851993         70 kill -9 19959
--可以看到object_type=INDEX,object_name=IDX_BITMAP_STATUS,指向锁等待发生在索引上,如果进一步查询索引类型,可以确认为bitmap位图索引
select index_type from dba_indexes a where owner='USER1' and index_name='IDX_BITMAP_STATUS';
INDEX_TYPE
---------------------------
BITMAP        
--另外,可以看到,等待的名称为enq: TX - row lock contention,等待的语句符合实验,阻塞的会话sid为62

3)查询等待的对象和具体的行数据  ---可以看到,这种锁等待,无法定位具体的数据行
set linesize 200 pagesize 999
col owner for a15
col object_name for a20
select inst_id,sid,row_wait_obj#, row_wait_file#, row_wait_block#, row_wait_row#,b.owner,b.object_name
from gv$session a,dba_objects b
where a.event like 'enq: TX%'
and a.row_wait_obj# = b.object_id(+);


INST_ID   SID ROW_WAIT_OBJ# ROW_WAIT_FILE# ROW_WAIT_BLOCK# ROW_WAIT_ROW# OWNER           OBJECT_NAME
------- ----- ------------- -------------- --------------- ------------- --------------- ------------------
      1    63         87485              0               0             0 USER1           IDX_BITMAP_STATUS
---可以看到,这种锁等待,无法定位具体的数据行

从这个实验可以看出来:位图索引不适合高并发 DML 的 OLTP 环境,即使修改的是不同行,只要涉及同一个位图索引键值(列),就会产生严重的 TX mode 4 等待!!

3、解决方案

根本原因分析:位图索引存储的是键值 + 指向行ID(ROWID)的位图(Bitmap)

  • 当会话A修改(锁定)某个键值(如 STATUS='ACTIVE')下的任意一行 时,Oracle会锁定整个位图段(Bitmap Segment)
  • 当会话B也想修改同一个键值 下的另一行时,它发现该键值的整个位图已被会话A锁定(即使不是同一行),必须等待会话A提交或回滚。
  • 锁粒度极粗 :一个位图段通常覆盖半个数据块到多个数据块的行。因此,并发修改同一键值,本质上是在排队抢占同一把"大锁",直接导致严重的TX行锁等待。

解决方案:彻底移除位图索引,改用B树索引(强烈推荐)

如果这张表需要支持高并发的增删改(DML),请立即删除位图索引,并创建普通的B树索引(B-Tree Index)

在Oracle中,位图索引绝对不适合高并发的OLTP(在线事务处理)环境 ,位图索引专为读密集、批量加载、几乎无并发DML的数据仓库设计,而非OLTP DML(增删改频繁业务!)

Oracle官方文档明确指出,位图索引主要用于数据仓库不要在OLTP系统中对位图索引存在任何幻想,删除它是最彻底的解决方案。

七、CREATE INDEX ONLINE,引起的锁等待实验、分析和解决方案

1、实验验证

复制代码
SQL> create table big_table (id number,data varchar2(100));
Table created.
SQL> insert into big_table values(1,'test');
1 row created.
SQL> commit;
Commit complete.

会话1
SQL> UPDATE big_table SET data = 'MODIFIED' WHERE id = 1;    --DML操作,不提交
1 row updated.

会话2
CREATE INDEX idx_big_table_data ON big_table(data) ONLINE;   --被阻塞,无法完成

2、2、查询分析

复制代码
1)通过gv$lock视图查询,检查数据中是否存在锁等待
SESS       LMODE                CTIME INST_ID        ID1        ID2    REQUEST TY
---------- --------------- ---------- ------- ---------- ---------- ---------- --
Holder: 64 exclusive               32       1    1179664         72          0 TX
Waiter: 62 none                    13       1    1179664         72          4 TX
--可以看到REQUEST=4,请求MODE=4的锁,发生了等待

2)查询具体的TX锁等待会话,等待会话正在执行的语句,正在等待的对象,会话自身的连接属性等详细信息(建议使用连接工具查询,而非sqlplus方式)
2026-08-10 14:01 5h5b7gdhvzwsm enq: TX - row lock contention  USER1  oracle                         19941
host40rac1    sqlplus@host40rac1 (TNS V1-V3) SQL*Plus
CREATE INDEX idx_big_table_data ON big_table(data) ONLINE
2026-08-10/16:04:55   10-AUG-26 2026-08-10 16:13:52 USER1           BIG_TABLE                     539    62     0   1   60 TABLE   6066774 1415053316     983064         67 kill -9 19951

--等待的名称为enq: TX - row lock contention,等待的语句符合实验,阻塞的会话sid为60
--等待会话,正在执行的语句为CREATE INDEX idx_big_table_data ON big_table(data) ONLINE ,符合实验预期

从这个实验可以看出来:CREATE INDEX ONLINE执行时,如果表上已有未提交的 DML 事务,创建索引的操作会被阻塞。

3、解决方案

方法一、等待表上其它未提交的DML事务,完成提交

方法二、查询哪些会话对该表进行了DML事务,手工处理掉

方法三、创建索引的时间,选择业务低峰期(尽量避免这类情况)

相关推荐
yi.Ist43 分钟前
数据定义语言-DDL操作
数据库·学习·mysql·oracle·大海豚
2601_963282772 小时前
工程项目、政企采购为什么优先选择对讲机批量采购?
数据库
Doraemomo2 小时前
SQLite数据库
数据库·sqlite
云贝教育-郑老师3 小时前
MySQL 的“黑匣子“:Performance Schema 把数据库内部变成一张可查的表
数据库·mysql
这个DBA有点耶5 小时前
同城双活落地的三座山:网络延迟、脑裂预防、反向同步
数据库·架构·dba
青春之我_XP5 小时前
MySQL 常用日期函数 实战指南
数据库·sql·mysql·数据分析·数据库开发·日期函数
Nturmoils5 小时前
sys_dump 备了库,角色和权限别漏在外面
数据库
接着奏乐接着舞。5 小时前
【2026】73道Redis 常见面试题与参考答案
数据库·redis·后端·缓存