根本原因是oracle空间回收依赖active/expired区段分布而非使用率百分比:当tuned_undoretention被自动调优异常拉高(如达数万秒)或主机时间被向前调整导致undo过期判断紊乱时,unexpired区段长期堆积,虽物理空间充足但逻辑上“锁死”,仅在事务需新extent且无expired可用时才报ora-30036。

UNDO 表空间使用率 99% 不报错,根本原因不是“空间真没了”,而是 Oracle 的空间回收机制和状态判断逻辑决定了它不靠剩余空间百分比触发错误,而是依赖事务状态、保留策略与数据文件配置三者协同判断。
UNDO 表空间的“使用率”只是表象,真正决定是否报错的是 ACTIVE 和 EXPIRED 区段分布
ACTIVE 和 EXPIRED 区段分布Oracle 不会因为 dba_undo_extents 中显示 99% 被占就直接报 ORA-30036: unable to extend segment。它只在需要分配新 undo extent 时检查:有没有可复用的 EXPIRED 区段?如果没有,且数据文件不能自动扩展,才会失败。
-
ACTIVE区段:正在被未提交事务使用 → 必须保留,不可覆盖 -
UNEXPIRED区段:事务已提交,但还没到undo_retention(或调优后的tuned_undoretention)时间 → 理论上可被覆盖,但默认策略是“尽量不覆盖”,尤其当retention被自动拉高时 -
EXPIRED区段:已过期,可立即复用 → 这才是 Oracle 真正能“腾出”的空间
所以常见现象是:UNEXPIRED 占比长期 >95%,EXPIRED 几乎为 0 —— 空间物理存在,但逻辑上“锁死”,此时使用率 99% 也不会立即报错,直到某次大事务需要新 extent 且无 EXPIRED 可用。
undo_retention 被自动调优抬高,导致 UNEXPIRED 区段堆积
undo_retention 被自动调优抬高,导致 UNEXPIRED 区段堆积Oracle 10g+ 默认启用 _undo_autotune,它会根据表空间大小和历史最长查询时间动态计算 tuned_undoretention。一旦这个值被推高(比如从 900 秒变成 86400 秒甚至更大),所有刚提交的 undo 都要等一整天才能变 EXPIRED。
查证方式:
SELECT MAX(tuned_undoretention) max_tuned, MAX(maxquerylen) max_query_sec FROM v$undostat WHERE begin_time > SYSDATE - 1;
- 如果
max_tuned是几万秒,而max_query_sec才几十秒 → 典型自动调优失控 - RAC 环境中各节点
gv$undostat值差异大 → 某个节点有隐式长事务(如 DBLINK 查询卡住),拖累全局tuned_undoretention - 数据文件未开
AUTOEXTENSIBLE→ 自动调优更激进,因 Oracle 认为“空间紧张必须留久一点”
数据文件未启用自动扩展,但表空间仍不报错的隐藏前提
即使 dba_data_files.autoextensible = 'NO',只要还有空闲区段(哪怕只剩 1 个 64KB extent),Oracle 就不会报错。它只在尝试分配新 extent 时才检查——而这个动作是否发生,取决于事务负载模式。
- 低并发小事务:可能持续数小时都不触发分配,99% 使用率也能“假稳定”
- 批量 INSERT / UPDATE / 大表 JOIN:瞬间申请多个 extent,立刻暴露问题
- 注意:
dba_free_space对 UNDO 表空间统计不准 —— 它只反映物理空闲块,不反映逻辑可复用性(EXPIRED才是关键)
真正该查的是:
SELECT status, COUNT(*), ROUND(SUM(bytes)/1024/1024, 2) mb FROM dba_undo_extents WHERE tablespace_name = (SELECT UPPER(value) FROM v$parameter WHERE name = 'undo_tablespace') GROUP BY status;
主机时间被向前调整过,是极隐蔽但致命的“永久卡死”原因
如果系统时间曾被人为从未来调回过去(比如从 2026-09 调到 2026-03),Oracle 内部基于 SCN 和时间戳的 undo 过期判断就会紊乱。v$undostat 中的 begin_time 可能还停留在“未来日期”,导致所有对应 undo 都永远 UNEXPIRED。
- 现象:重启数据库后,
v$transaction.start_time仍显示几个月前的日期 - 验证:查
v$undostat.begin_time是否明显早于当前系统时间 - 解法:仅调回系统时间不够,必须重建 UNDO 表空间(新建
UNDOTBS2→ 切换 → 删除旧表空间)
这类问题不会在监控里直接体现为“错误”,只会表现为使用率长期钉死在 99%,且任何常规参数调整都无效 —— 因为底层时间戳已损坏。











