Oracle enq: US - contention 等待事件总结
一、原因概述
enq: US - contention 中的 US = Undo Segment。
这个等待事件表示:会话需要获取一个 UNDO 段来存放事务的 UNDO 数据,但在获取过程中发生了排队等待。
该等待事件不是因为 UNDO 表空间没有空间了(空间不足会直接报 ORA-30036),而是 UNDO 段的分配路径被堵住了(阻塞)。
二、内部机制
UNDO 段的三种状态
UNDO 表空间中的区(Extent)有三种状态:
┌──────────┬─────────────────────────────────────────────────────┐
│ 状态 │ 含义 │
├──────────┼─────────────────────────────────────────────────────┤
│ ACTIVE │ 正在被活跃事务使用,不可回收 │
│ UNEXPIRED│ 事务已提交,但还在 UNDO_RETENTION 保留期内,不可回收 │
│ EXPIRED │ 已过保留期,可以被回收重用 │
└──────────┴─────────────────────────────────────────────────────┘
空间分配优先级:
1. 先找 FREE 空间(从未使用过的)
2. 再找 EXPIRED 空间(过期的,可以回收)
3. 再找 OFFLINE UNDO 段中的空间
4. 如果都找不到 → ORA-30036(UNDO 表空间真的没空间了)
争用链路
新事务开始 → 需要 UNDO 段来存放修改前镜像
→ 当前已 ONLINE 的 UNDO 段中没有足够的 FREE 空间
→ Oracle 需要在 OFFLINE 的 UNDO 段中搜索可用空间
→ 搜索过程需要访问数据字典缓存 dc_rollback_segments
→ dc_rollback_segments 受 row cache objects latch 保护
→ 大量会话同时搜索 → latch 争用 + US enqueue 争用
→ enq: US - contention
三、三种典型触发场景
场景一:_undo_autotune 导致 UNDO 段频繁上下线
Oracle 的 _undo_autotune 机制(默认开启)
1、_undo_autotune 作用
_undo_autotune = TRUE 时:
Oracle 定期(约每5分钟)统计:
→ 当前运行时间最长的查询已经跑了多久
→ 将 undo_retention 自动调高到该值 + 余量
例如:
T1: 最长查询跑了 300 秒 → Oracle 自动将 retention 调到 600
T2: 最长查询跑了 1800 秒 → Oracle 自动将 retention 调到 3600
T3: 最长查询跑了 18000 秒 → Oracle 自动将 retention 调到 36000
影响:
retention 调高 → 更多已提交的 extent 被标记为 UNEXPIRED(不能回收)
→ 可用 FREE/EXPIRED 空间减少
→ 但段本身仍然是 ONLINE 状态!
2、导致enq: US - contention的因果链
_undo_autotune = TRUE时
│
├→ 自动调高 undo_retention(比如调到 36000 秒)
│
├→ 大量已提交的 extent 被标记为 UNEXPIRED(不能回收)
│ ACTIVE: 50 MB (被活跃事务使用)
│ UNEXPIRED: 3200 MB (被 retention 占着,不能回收) ← 问题在这里
│ EXPIRED: 96 MB (可以回收)
│ FREE: 100 MB (剩余可用)
│
├→ 突发高并发 DML 涌入
│ → 需要大量 UNDO 空间
│ → 发现 FREE 和 EXPIRED 空间不足
│
├→ Oracle 需要分配新的 extent 或回收 UNEXPIRED extent
│ → 如果 UNDO 表空间有 AUTOEXTEND → 扩展数据文件
│ → 如果需要回收 UNEXPIRED → 可能触发段级操作
│ → 多个会话同时竞争 → enq: US - contention
│
└→ 关键:争用的是"空间分配路径",不是"段的 ONLINE/OFFLINE 状态"
场景二:长查询占用大量 UNEXPIRED 空间
T1: 一个长查询运行了 30 分钟
→ Oracle 自动调高 UNDO_RETENTION 以满足读一致性
→ 大量 UNDO 区被标记为 UNEXPIRED(不能回收)
→ 可用 FREE 空间减少
T2: 突发高并发 DML
→ 需要 UNDO 空间,但 FREE 不足
→ 去 OFFLINE 段中搜索 → 争用
→ enq: US - contention
场景三:大量 OFFLINE 段需要同时 ONLINE
T1: 应用重启 / 空闲状态突然繁忙(大量并发事务)
→ 大量新事务同时涌入
→ 需要将大量 OFFLINE 的 UNDO 段变成 ONLINE
→ SMON 处理不过来
→ 同时伴随 latch: row cache objects (on DC_ROLLBACK_SEGMENTS)
→ enq: US - contention
四、等待事件参数解释(p1、p2、p3 )
sql
-- 查看参数定义
SELECT name, parameter1, parameter2, parameter3
FROM v$event_name
WHERE name = 'enq: US - contention';
-- 输出:
-- NAME PARAMETER1 PARAMETER2 PARAMETER3
-- -------------------- ---------- -------------- ----------
-- enq: US - contention name|mode undo segment # 0
| 参数 | 含义 | 说明 |
|---|---|---|
| p1 | `name | mode` |
| p2 | undo segment # |
争用的 UNDO 段号 |
| p3 | 0 |
无意义,始终为 0 |
sql
-- 解码 p1
SELECT
CHR(BITAND(p1, POWER(2,32)-1) / POWER(2,16)) AS lock_type_1,
CHR(BITAND(p1, POWER(2,24)-1) / POWER(2,8)) AS lock_type_2,
BITAND(p1, POWER(2,16)-1) AS lock_mode
FROM v$session
WHERE event = 'enq: US - contention';
-- 输出示例:
-- LOCK_TYPE_1 LOCK_TYPE_2 LOCK_MODE
-- ----------- ----------- ---------
-- U S 4 ← US 锁,mode 4(Share)
五、排查分析语句
sql
-- 1. 查看当前 UNDO 配置
SHOW PARAMETER undo;
-- 输出示例:
-- NAME TYPE VALUE
-- ------------------- ------ ---------
-- undo_management string AUTO
-- undo_retention integer 900
-- undo_tablespace string UNDOTBS1
-- 2. 查看当前 UNDO 段状态
SELECT
rn.usn,
rn.name,
rs.status,
rs.rssize / 1024 / 1024 AS size_mb,
rs.xacts,
rs.waits,
rs.gets,
ROUND(rs.waits / NULLIF(rs.gets, 0) * 100, 2) AS wait_pct
FROM v$rollname rn
JOIN v$rollstat rs ON rn.usn = rs.usn
ORDER BY rs.waits DESC;
-- 3. 查看 UNDO 区状态分布
SELECT
status,
COUNT(*) AS extent_count,
SUM(bytes) / 1024 / 1024 AS size_mb
FROM dba_undo_extents
GROUP BY status;
-- 如果 UNEXPIRED 占比极高,说明 retention 设太大或 _undo_autotune 在捣乱
-- 输出示例:
-- STATUS EXTENT_COUNT SIZE_MB
-- --------- ------------ -------
-- ACTIVE 12 48.0 ← 正在被事务使用
-- UNEXPIRED 856 3200.0 ← 被 retention 占着,不能回收
-- EXPIRED 24 96.0 ← 可以回收
-- 问题:UNEXPIRED 占了 3.2GB,FREE 空间很少!
-- 4. 查看 OFFLINE 的 UNDO 段数量
SELECT segment_name, status, tablespace_name
FROM dba_rollback_segs
WHERE status = 'OFFLINE';
-- 5. 查询隐含参数 _undo_autotune
col name format a25
col isdefault format a15
col ismod format a15
col isadj format a15
col value format a15
set linesize 150 pagesize 999
select
x.ksppinm name,
y.ksppstvl value,
y.ksppstdf isdefault,
decode(bitand(y.ksppstvf,7),1,'MODIFIED',4,'SYSTEM_MOD','FALSE') ismod,
decode(bitand(y.ksppstvf,2),2,'TRUE','FALSE') isadj
from
sys.x$ksppi x,
sys.x$ksppcv y
where
x.inst_id = userenv('Instance') and
y.inst_id = userenv('Instance') and
x.indx = y.indx and
x.ksppinm like '%_&par%'
order by
translate(x.ksppinm, ' _', ' ')
/
NAME VALUE ISDEFAULT ISMOD ISADJ
------------------------- --------------- --------------- --------------- ---------------
_undo_autotune TRUE TRUE FALSE FALSE
-- 6.确认是否伴随 latch: row cache objects 争用
SELECT
s.sid,
s.event,
s.p1text, s.p1,
s.seconds_in_wait
FROM v$session s
WHERE s.event = 'latch: row cache objects'
AND s.type = 'USER';
-- 7.查看 dc_rollback_segments 的 gets 是否异常高
SELECT parameter, gets, GETMISSES
FROM v$rowcache
WHERE parameter = 'dc_rollback_segments';
-- 8. 历史会话查询确认 enq: US - contention 严重程度
SELECT
event,
COUNT(*) AS cnt,
SUM(time_waited) / 1000000 AS total_wait_sec
FROM v$active_session_history
WHERE event = 'enq: US - contention'
AND sample_time > SYSDATE - 1/24
GROUP BY event;
-- 9. 历史会话查询确认是否伴随 latch: row cache objects 争用
SELECT
event,
p1text, p1,
COUNT(*) AS cnt
FROM v$active_session_history
WHERE event IN ('enq: US - contention', 'latch: row cache objects')
AND sample_time > SYSDATE - 1/24
GROUP BY event, p1text, p1
ORDER BY cnt DESC;
六、解决方案
方案一:禁用 _undo_autotune(直接有效,推荐)
sql
-- 关闭自动调优,防止 Oracle 自动调大 retention 导致 UNEXPIRED 膨胀
ALTER SYSTEM SET "_undo_autotune" = FALSE SCOPE=BOTH;
-- 手动设置一个合理的 UNDO_RETENTION
-- 根据业务最长查询时间设定,不要盲目设大
ALTER SYSTEM SET undo_retention = 900 SCOPE=BOTH; -- 15 分钟
| 优点 | 缺点 |
|---|---|
| 立即生效,无需重启 | 需要手动管理 retention 值 |
| 防止 UNDO 段频繁上下线 | 如果设太小,长查询可能报 ORA-01555 |
方案二:增大 UNDO 表空间(直接有效,推荐)
sql
-- 增加 UNDO 数据文件
ALTER TABLESPACE undotbs1
ADD DATAFILE '/u01/app/oracle/oradata/ORCL/undotbs02.dbf'
SIZE 10G AUTOEXTEND ON NEXT 1G MAXSIZE 30G;
-- 或者扩展现有数据文件
ALTER DATABASE DATAFILE '/u01/app/oracle/oradata/ORCL/undotbs01.dbf'
RESIZE 20G;
方案三:阻止 SMON 自动 OFFLINE UNDO 段(不推荐)
sql
-- 设置 event 10511,阻止 SMON 自动将 UNDO 段 OFFLINE
ALTER SYSTEM SET EVENTS '10511 trace name context forever, level 1';
-- 问题修复后取消
ALTER SYSTEM SET EVENTS '10511 trace name context off';
方案四:设置 retention 自动调整的上限(不推荐)
-- 允许自动调整,但设一个天花板
ALTER SYSTEM SET "_highthreshold_undoretention" = 3600 SCOPE=BOTH;
-- 即使 Oracle 自动调高 retention,也不会超过 3600 秒
方案五:提前固定 ONLINE 的 UNDO 段数量(不推荐)
sql
-- 提前设置 _rollback_segment_count,保持足够多的 UNDO 段始终 ONLINE
ALTER SYSTEM SET "_rollback_segment_count" = 50 SCOPE=SPFILE;
-- 需要重启生效
-- 或者手动 ONLINE 足够的 UNDO 段
-- 建议数量 = 峰值并发事务数 × 1.5,但不超过 64