UNDO表空间是Oracle数据库中用于存放回滚数据的特殊表空间,所有DML操作产生的 undo 数据都会先写到这里。不少DBA都遇到过这样的场景:业务正常运行,undo表空间却一路膨胀,从几个GB涨到几十GB甚至上百GB,最后磁盘写满、数据库挂起。更让人困惑的是,即使把大事务杀掉了,undo表空间的空间也不见释放。这篇文章就来系统地分析undo表空间增长过快的原因,并给出排查和处理的完整思路。

先搞懂UNDO表空间的空间是怎么管理的
Oracle从9i开始引入自动undo管理模式(AUM),由undo_management=AUTO参数控制。在这种模式下,DBA不再需要手工管理回滚段,Oracle会在undo表空间内自动创建一批undo段,事务开始时从SMON持有的公共undo段池中获取可用的段来写undo数据。理解空间为什么不释放,关键在于理解一个概念:undo表空间的空间只会被复用,不会被主动归还给操作系统。
也就是说,数据文件一旦自动扩展到100GB,即使里面的undo数据早就过期了,这100GB的空间仍然是undo表空间的已分配空间。Oracle的做法是把过期区的空间标记为可复用,后续事务优先使用这些过期区,而不是继续扩展文件。所以看到undo表空间使用率高,首先要区分两种情况:一种是文件确实不够大,活动undo加上未过期的undo撑满了空间;另一种是文件很大但里面大部分是过期区,只是空间没被回收,这种情况下其实不需要处理,空间会在后续被自动复用。
可以通过下面的SQL查看undo表空间内部区的状态分布,这是判断问题性质的第一步:
-- 查看undo表空间中各个状态区的数量和大小
SELECT tablespace_name,
status,
COUNT(*) AS extent_count,
ROUND(SUM(bytes)/1024/1024) AS size_mb
FROM dba_undo_extents
GROUP BY tablespace_name, status
ORDER BY tablespace_name, status;
查询结果中ACTIVE表示正在被活动事务使用,UNEXPIRED表示已经提交但还未超过undo_retention保留时间的区,EXPIRED表示已超过保留时间、可以被复用的区。如果EXPIRED占了绝大部分,说明只是空间没回收,属于正常现象;如果UNEXPIRED或ACTIVE占比很高,就要继续深挖原因。
UNDO表空间快速增长的四类典型原因
1. 大事务产生海量undo数据
这是最直接的原因。一次性的大批量UPDATE、DELETE,或者批量INSERT带LOB操作,都会在短时间内产生巨量undo。比如一张几亿行的表做全表UPDATE,undo数据量可能比表本身还大。这类事务如果运行时间很长且未提交,对应的undo区会一直保持ACTIVE状态,无法回收也无法复用,undo表空间只能不断扩展。
排查时可以查询v$transaction和v$session的关联视图,找到undo块消耗大户:
-- 找出当前消耗undo最多的会话
SELECT s.sid,
s.serial#,
s.username,
s.machine,
t.start_time,
ROUND(t.used_urec/10000, 1) AS undo_records_wan,
ROUND(d.used_ublk * 8192/1024/1024, 1) AS undo_mb
FROM v$transaction t, v$session s, v$rollstat d
WHERE t.ses_addr = s.saddr
AND t.xidusn = d.usn
ORDER BY undo_mb DESC;
如果发现某个会话的undo占用持续增长,基本可以锁定是应用里存在大事务。解决办法是把大批量操作改成分批提交,比如每处理1万行提交一次,既能控制undo消耗,也能减少行锁持有时间。
2. undo_retention参数设置过大
undo_retention决定了undo数据提交后保留的秒数,主要为了保证长时间的查询和闪回操作不会碰到ORA-01555快照过旧的错误。有些DBA为了保险把它设成几个小时甚至几天,结果就是大量已经提交的undo长期处于UNEXPIRED状态,白白占用空间。 undo_retention默认值是900秒,如果业务上没有超长查询需求,一般不需要调得太大。
3. 长查询导致undo无法过期
这是最容易被忽视的一类原因。Oracle为了保证一致性读,查询开始时会记录当前SCN,查询期间读到的数据块如果已经被修改,就需要通过undo回滚出旧版本。如果有一条SQL跑了三个小时,那么从查询开始那一刻起产生的所有undo都不能过期回收,否则查询就会报ORA-01555。所以一条失控的报表SQL,可能让三个小时的undo数据全部保持在UNEXPIRED状态。排查这类问题要重点看v$undostat视图:
-- 查看最近一段时间undo使用统计,每行代表10分钟
SELECT begin_time,
end_time,
maxquerylen,
undoblks,
txncount,
ssolderrcnt,
nospaceerrcnt
FROM v$undostat
ORDER BY begin_time DESC;
其中maxquerylen是这10分钟内最长查询的秒数,如果这个值很高,就说明存在长查询在绑架undo保留时间;ssolderrcnt是ORA-01555发生的次数;nospaceerrcnt是空间不足报ORA-30036的次数,这几个字段组合起来基本能还原undo空间的使用全貌。历史趋势可以查dba_hist_undostat。
4. 数据文件自动扩展参数设置不当
还有一个隐性问题:很多系统的undo数据文件设置了AUTOEXTEND ON NEXT 512M MAXSIZE UNLIMITED,遇到前面几种情况时文件会一路扩展,等发现时已经很大了。建议给maxsize设一个合理上限,这样即使出问题也会提前报ORA-30036暴露出来,而不是默默把磁盘吃光。
UNDO表空间过大如何收缩
undo表空间的数据文件即使里面的区都过期了,也无法直接用alter database datafile ... resize成功收缩,因为总有一些undo段的事务表等结构占着高水位以下的块。标准的处理办法是新建一个undo表空间,切换过去,再删掉旧的。整个操作可以在线完成,步骤如下:
-- 第一步:新建一个合适大小的undo表空间 CREATE UNDO TABLESPACE UNDOTBS2 DATAFILE '/oradata/prod/undotbs2_01.dbf' SIZE 8G AUTOEXTEND ON NEXT 512M MAXSIZE 32G; -- 第二步:切换undo表空间(动态生效,无需重启) ALTER SYSTEM SET undo_tablespace = UNDOTBS2; -- 第三步:等待旧表空间上的活动事务全部结束 SELECT a.owner, a.segment_name, b.status FROM dba_rollback_segs a, v$rollstat b WHERE a.segment_id = b.usn AND a.tablespace_name = 'UNDOTBS1' AND b.status = 'ONLINE'; -- 查不到 ONLINE 状态的旧回滚段后,旧的段会变为 OFFLINE -- 第四步:确认无活动事务后删除旧表空间 DROP TABLESPACE UNDOTBS1 INCLUDING CONTENTS AND DATAFILES;
几个细节要注意:切换参数后,新事务会立刻使用新表空间,但旧表空间上未结束的事务仍会继续使用旧undo段,所以第三步的等待是必须的;如果旧表空间上有Active的undo段删不掉,说明还有事务没提交,可以先等或者杀掉对应会话;删除前务必确认没有应用在闪回查询中引用旧表空间的undo数据。
如何预防UNDO表空间再次失控
处理完问题只是第一步,更关键的是建立预防机制。首先在应用层面规范大事务的写法,大批量DML必须分批提交,单次事务的undo量控制在几百MB以内。其次合理设置undo_retention,如果开了闪回或者有长查询需求,可以适当调大,但要清楚代价是undo空间的增长;如果空间紧张,可以考虑设置undo_retention为0并让Oracle根据表空间大小自动调整保留时间,也就是保证撤销表空间有10G的自动扩展空间时Oracle会尽量延长保留期。
另外建议部署定期监控,用下面的脚本抓取undo使用情况并告警:
-- 监控undo表空间使用率,超过85%告警
SELECT d.tablespace_name,
ROUND((d.used_mb - f.free_mb) / d.used_mb * 100, 2) AS used_pct,
d.used_mb,
f.free_mb
FROM (SELECT tablespace_name, ROUND(SUM(bytes)/1024/1024) AS used_mb
FROM dba_data_files
WHERE tablespace_name LIKE 'UNDOTBS%'
GROUP BY tablespace_name) d,
(SELECT tablespace_name, ROUND(SUM(bytes)/1024/1024) AS free_mb
FROM dba_free_space
WHERE tablespace_name LIKE 'UNDOTBS%'
GROUP BY tablespace_name) f
WHERE d.tablespace_name = f.tablespace_name;
最后再强调一点认知上的误区:看到undo表空间使用率90%以上就紧张想收缩,其实是没必要的。在AUM模式下,高使用率反而是常态,Oracle会优先复用过期区。真正需要关注的是nospaceerrcnt是否增长、事务是否频繁报ORA-30036。把监控的重点从空间使用率转移到错误计数和最长查询时长上,才能做到既不浪费空间,也不影响业务。
Oracle UNDO表空间undo tablespaceORA-30036修改时间:2026-09-10 04:36:41