Oracle TX 锁 Mode 4(Share)问题排查总结
一、概述
上一篇文章《Oracle 行锁问题排查总结(enq: TX - row lock contention)》,enq: TX - row lock contention 锁等待事件中,P1 参数值将请求锁的名称和请求模式编码在一起,可以通过查看 P1RAW 的十六进制值来解析(通过公式 name<<16 | mode 计算),或者通过 SQL 函数转换,具体如下
-
name(锁名称) :标识锁的类型。对于行锁,其名称固定为TX。TX的 固定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数据块头部的一组"停车位"或"槽位"。
- 作用 :任何一个事务(如
UPDATE、DELETE)在修改数据块中的行之前,都必须先在块头部的ITL中占用一个槽位。这个槽位会记录该事务的ID、回滚段地址等关键信息。 - 记录内容 :每个ITL槽位主要记录事务ID (XID) 、回滚段地址 (UBA) 、事务状态 (活跃/已提交)以及锁定的行数 (Lck)。
- 复用机制 :当一个事务提交(
COMMIT)或回滚(ROLLBACK)后,它占用的ITL槽位就会被释放,供其他新事务复用。
Oracle的ITL数量是动态的,其自动扩展遵循以下逻辑:
- 初始分配 :数据块在创建时,会根据对象的
INITRANS参数预先分配一定数量的ITL槽位。 - 按需扩展 :当并发事务数超过了当前已有的ITL槽位数时,Oracle会尝试在数据块头部动态地创建一个新的ITL槽位。
- 扩展条件 :这个动态扩展不是无限制的 ,必须同时满足两个条件:
- 有足够的空闲空间 :数据块的
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事务,手工处理掉
方法三、创建索引的时间,选择业务低峰期(尽量避免这类情况)