Oracle enq: US - contention 等待事件总结

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
相关推荐
姚不倒2 小时前
etcd 学习系列(一):从业务需求出发,理解 etcd 是什么
运维·数据库·etcd
susplus2 小时前
【linux应用软件编程】数据库sqlite3
linux·数据库·sqlite
SelectDB2 小时前
Apache Doris + Lance:让多模态数据真正进入智能驾驶与具身智能的分析闭环
数据库
互联网叫兽2 小时前
redis深入学习二
数据库·redis·学习
疯狂打码的少年2 小时前
【数据库技术】复习日:关系代数 + SQL + 规范化(整理对比表)
jvm·数据库·笔记·sql
先吃饱再说3 小时前
从零开发到工程查询:SQLite 嵌入式数据库与高级 SQL 实战
数据库
ShineWinsu3 小时前
对于MySQL:数据库的操作的解析
linux·数据库·c++·mysql·面试·笔试·库的操作
‎ദ്ദിᵔ.˛.ᵔ₎3 小时前
MySQL 数据类型
数据库·mysql
这个DBA有点耶3 小时前
索引合并不是万能药:MySQL同时用两个索引,为什么比不用还慢?
数据库·mysql·dba