Oracle UNDO表空间管理维护总结
一、UNDO概述
1.1 什么是UNDO
UNDO(撤销)数据是Oracle数据库中用于回滚或撤销对数据库所做更改的信息。这些信息由事务操作记录组成,主要在事务提交之前产生,统称为UNDO数据。
UNDO表空间是Oracle数据库中用于存储数据修改前镜像(Before Image) 的专用表空间。每次执行增删改UPDATE、DELETE或INSERT操作时,Oracle都会在修改数据块之前,先将原始值记录到UNDO表空间中,然后才修改数据块。
1.2 UNDO的核心用途
| 用途 | 说明 |
|---|---|
| 事务回滚(Rollback) | 当用户执行ROLLBACK命令或事务异常中断时,Oracle使用UNDO数据将已修改的数据恢复到事务开始前的状态,保证事务的原子性 |
| 读一致性(Read Consistency) | 当一个事务正在查询数据时,其他事务对同一数据的修改不会影响到当前查询的结果。 查询开始时记录当前SCN,如果读取的数据块SCN大于查询的SCN,Oracle从UNDO中找到该行的旧版本 |
| 闪回查询(Flashback Query) | Oracle闪回查询(Flashback Query)、闪回事务(Flashback Transaction)、闪回版本查询(Flashback Version Query)等特性,都依赖UNDO表空间中保留的历史版本数据。 |
| 实例恢复(Instance Recovery) | 数据库崩溃重启后,Oracle使用UNDO数据回滚未提交的事务,保证数据一致性 |
1.3 UNDO与REDO的区别
| 对比维度 | UNDO | REDO |
|---|---|---|
| 记录内容 | 数据修改前的旧值(Before Image) | 数据修改的操作(Change Vector) |
| 主要用途 | 回滚事务、读一致性 | 前滚恢复、重演事务 |
| 产生时机 | 修改数据前记录 | 修改数据时记录 |
| 存储位置 | UNDO表空间 | 联机重做日志文件 |
特别注意:记录UNDO数据时,同样需要产生redo日志!与其他业务表增删改时产生的redo日志没有任何区别!
二、UNDO的两种管理方式
Oracle数据库提供了两种UNDO管理方式:Manual Undo Management(手动UNDO管理) 和Automatic Undo Management(自动UNDO管理)。
2.1 Manual Undo Management(手动UNDO管理)
在Oracle 9i之前,管理UNDO数据只能使用回滚段(Rollback Segment)。在这种模式下,UNDO空间通过回滚段外部进行分配,不使用UNDO表空间。
特点:
- DBA需要手动创建、管理和监控回滚段
- 需要规划回滚段的数量和大小
- 管理复杂,容易因回滚段不足导致事务失败
- 通过参数
UNDO_MANAGEMENT=MANUAL启用
备注:该管理模式,基本已经完全弃用
2.2 Automatic Undo Management(自动UNDO管理)
Oracle 9i引入了自动UNDO管理,通过UNDO表空间来管理UNDO段,消除了回滚段管理的复杂性。
特点:
- 数据库自动管理UNDO段
- 使用UNDO表空间管理和存储UNDO段
- 自动调整UNDO保留时间
- 大大简化了DBA的管理工作
- 通过参数
UNDO_MANAGEMENT=AUTO启用
强烈建议 :Oracle官方强烈推荐使用UNDO表空间而非回滚段来管理UNDO。在CDB中,
UNDO_MANAGEMENT必须设置为AUTO。备注:现在全部都是使用这种自动undo管理方式!
三、UNDO表空间创建、删除、切换、扩容
3.1 创建UNDO表空间
基本语法:
CREATE UNDO TABLESPACE undo_tbs_name
DATAFILE 'datafile_path' SIZE size
[AUTOEXTEND ON NEXT increment_size MAXSIZE max_size]
[RETENTION GUARANTEE];
参数说明:
RETENTION NOGUARANTEE:默认值,当UNDO空间不足时,允许覆盖未过期的UNDO数据。RETENTION GUARANTEE:保证未过期的UNDO数据不会被覆盖,即使导致DML事务失败(详见第六模块)。
注意事项:
- UNDO表空间必须使用本地管理(
EXTENT MANAGEMENT LOCAL),不允许指定区管理方式,Oracle会自动采用AUTOALLOCATE。 - 不能在UNDO表空间中创建任何数据库对象(表、索引等)。
- RAC环境中每个实例必须有独立的UNDO表空间,不允许共享
示例:
sql
-- 创建50M的UNDO表空间,启用自动扩展
CREATE UNDO TABLESPACE undotbs2
DATAFILE '/oradata/orcl/undotbs02.dbf' SIZE 50M
AUTOEXTEND ON NEXT 20M MAXSIZE 500M;
-- 创建带RETENTION GUARANTEE的UNDO表空间
CREATE UNDO TABLESPACE undotbs3
DATAFILE '/oradata/orcl/undotbs03.dbf' SIZE 20M
RETENTION GUARANTEE;
3.2 删除UNDO表空间
前提条件:不能删除当前正在使用的UNDO表空间。
sql
-- 1. 先切换到其他UNDO表空间
ALTER SYSTEM SET UNDO_TABLESPACE = undotbs2;
-- 2. 确认切换完成
SHOW PARAMETER undo_tablespace;
-- 3. 删除旧的UNDO表空间
DROP TABLESPACE undotbs1 INCLUDING CONTENTS AND DATAFILES;
注意:
1、在数据库中删除表空间后,数据文件可能仍在物理磁盘上存在,需要手动删除。
2、如果待删除的undo表空间中,还有未结束的活动事务,需要等待事务完成才可以删除。即表空间中所有UNDO段必须处于离线状态(所有事务已完成)
3.3 切换默认UNDO表空间
任意给定时刻只能使用一个UNDO表空间。
sql
-- 切换默认UNDO表空间
ALTER SYSTEM SET UNDO_TABLESPACE = undotbs3;
3.4 扩容UNDO表空间
方法一:添加数据文件
sql
ALTER TABLESPACE undotbs1
ADD DATAFILE '/oradata/orcl/undotbs02.dbf' SIZE 1G
AUTOEXTEND ON NEXT 100M MAXSIZE 2048M;
方法二:调整现有数据文件大小
sql
ALTER DATABASE DATAFILE '/oradata/orcl/undotbs01.dbf' RESIZE 2G;
方法三:启用/调整自动扩展
sql
ALTER DATABASE DATAFILE '/oradata/orcl/undotbs01.dbf'
AUTOEXTEND ON NEXT 128M MAXSIZE 24G;
四、UNDO表空间使用
4.1 UNDO段(Undo Segment)
UNDO段是UNDO表空间中用于存储UNDO记录的逻辑结构。每个活跃的事务会分配到一个UNDO段,该事务的所有UNDO记录都写入这个UNDO段中。
特别注意:一个UNDO段可以同时支持多个事务!
4.2 UNDO段,区(extent)的状态
| 状态 | 说明 |
|---|---|
| ACTIVE | 正在被活跃事务使用,不可覆盖 |
| UNEXPIRED | 事务已提交但未超过UNDO_RETENTION,不可覆盖(除非空间不足) |
| EXPIRED | 已超过UNDO_RETENTION,可以被覆盖 |
| FREE(空闲) | 未分配的UNDO空间 |
4.3 空间分配优先级
当新事务需要UNDO空间时,Oracle按以下顺序分配:
- 优先使用
FREE空闲空间。 - 空闲空间不足时,使用
EXPIRED已过期的UNDO空间。 - 已过期空间仍不足时,若未开启
RETENTION GUARANTEE,会覆盖UNEXPIRED未过期的UNDO数据;若开启了GUARANTEE,则直接报错ORA-30036(无法扩展UNDO段)
sql
-- 查看UNDO表空间中各段的状态
SELECT segment_name, tablespace_name, status
FROM dba_rollback_segs
WHERE tablespace_name = 'UNDOTBS1';
-- 查看UNDO区段的状态分布
SELECT status, SUM(bytes)/1024/1024 AS size_mb
FROM dba_undo_extents
WHERE tablespace_name = 'UNDOTBS1'
GROUP BY status;
五、UNDO Retention的意义
5.1 什么是UNDO_RETENTION
UNDO_RETENTION是一个初始化参数,用于指定UNDO数据保留的最低时间阈值 (单位:秒)。默认值为900秒(15分钟)。
- 保障长时间运行的查询能够获取读一致性数据,避免ORA-01555错误。
- 支撑闪回查询的时间范围,例如设置为3600秒,则支持查询1小时内的历史数据。
sql
-- 查看当前UNDO_RETENTION
SHOW PARAMETER undo_retention;
-- 修改UNDO_RETENTION(动态生效)
ALTER SYSTEM SET UNDO_RETENTION = 1800;
5.2 不同场景下UNDO_RETENTION的生效规则
- 自动扩展的UNDO表空间 :Oracle会尽量满足
UNDO_RETENTION设定的保留时间,当空间不足时会自动扩展数据文件。 - 固定大小的UNDO表空间(默认) :
UNDO_RETENTION仅作为参考值,当空间不足时,Oracle会自动缩短实际保留时间,优先保障DML事务的执行。 - 开启RETENTION GUARANTEE的UNDO表空间:严格保证未过期的UNDO数据不会被覆盖,即使导致DML事务失败也不会妥协。
5.3 如何确保UNDO数据不被提前覆盖
方法一:使用RETENTION GUARANTEE
从Oracle 10g开始,可以使用RETENTION GUARANTEE选项,确保在定义的UNDO_RETENTION时间之前,UNDO信息不会被覆盖。
sql
-- 创建时指定
CREATE UNDO TABLESPACE undotbs1
DATAFILE '/path/undotbs01.dbf' SIZE 200M
RETENTION GUARANTEE;
-- 对现有表空间修改
ALTER TABLESPACE undotbs1 RETENTION GUARANTEE;
-- 取消GUARANTEE
ALTER TABLESPACE undotbs1 RETENTION NOGUARANTEE;
特别注意:
1、如果设置UNDO表空间为
RETENTION GUARANTEE,未过期的数据不会被覆盖。如果表空间空间不足,会导致DML操作失败或事务挂起。2、开启
GUARANTEE后必须确保UNDO表空间容量足够,否则高并发DML场景下会频繁出现ORA-30036错误,导致业务事务失败
5.4 自动UNDO保留调优(Automatic Undo Retention Tuning)
从Oracle 10g开始,Oracle会根据UNDO表空间大小、UNDO生成速率、最长查询长度和RETENTION GUARANTEE设置等参数,自动调整UNDO保留时间 。设置的UNDO_RETENTION参数只是一个指导值,Oracle会自动调整UNDO保留时间以避免出现ORA-1555错误。
保留时间间隔依赖于估计最长的事务可能运行的时间长度。根据数据库中最长事务长度的信息,可以给UNDO_RETENTION分配一个大致的时间。可以通过v$undostat视图的maxquerylen列查询在过去的一段时间内,最长的查询执行的时间(以秒为单位)。UNDO_RETENTION参数中的时间会自动调整尽量大于等于maxquerylen列中给出时间值。
sql
-- 查看系统自动调整后的实际UNDO保留时间
SELECT TO_CHAR(begin_time, 'DD-MON-RR HH24:MI') AS begin_time,
TO_CHAR(end_time, 'DD-MON-RR HH24:MI') AS end_time,
tuned_undoretention
FROM v$undostat
ORDER BY end_time;
类似如下:
BEGIN_TIME END_TIME TUNED_UNDORETENTION
------------------ ------------------ -------------------
05-AUG-26 14:02 05-AUG-26 14:12 1172
05-AUG-26 14:12 05-AUG-26 14:22 1782
05-AUG-26 14:22 05-AUG-26 14:32 1186
05-AUG-26 14:32 05-AUG-26 14:42 1733
05-AUG-26 14:42 05-AUG-26 14:52 82995
05-AUG-26 14:52 05-AUG-26 15:02 68969
05-AUG-26 15:02 05-AUG-26 15:02 68969
其中,TUNED_UNDORETENTION列值,就是自动调整后undo的保留时间
六、如何评估所需UNDO大小
6.1 计算公式
评估UNDO表空间大小需要以下三个信息:
| 参数 | 说明 |
|---|---|
| UR | UNDO_RETENTION参数值(单位:秒) |
| UPS | 每秒生成的UNDO数据块数量 |
| DBS | 数据库块大小(db_block_size) |
计算公式:
UndoSpace = [UR × (UPS × DBS)]
6.2 计算步骤
步骤1:确定UNDO_RETENTION
sql
SHOW PARAMETER undo_retention;
步骤2:计算每秒UNDO块生成量(UPS)
sql
-- 计算业务高峰期每秒产生的UNDO数据块数
SELECT MAX(undoblks / ((end_time - begin_time) * 24 * 3600)) AS ups
FROM v$undostat;
步骤3:获取数据库块大小
sql
SHOW PARAMETER db_block_size;
步骤4:计算所需UNDO空间
sql
SELECT (ur * (ups * dbs)) AS "Bytes"
FROM (SELECT value AS ur FROM v$parameter WHERE name = 'undo_retention'),
(SELECT (SUM(undoblks) / SUM(((end_time - begin_time) * 86400))) AS ups
FROM v$undostat),
(SELECT value AS dbs FROM v$parameter WHERE name = 'db_block_size');
示例计算:
假设:
-
UNDO_RETENTION = 86400秒(24小时) -
UPS = 11.305块/秒 -
db_block_size = 8192字节所需UNDO空间 = 11.305 × 86400 × 8192 / 1024 / 1024 / 1024 ≈ 7.45 GB
建议:
1、一般应该在一天中数据库负载最繁重的时候 进行计算。同时参考UNDO表空间的平均使用情况和峰值使用情况。
2、生产环境的UNDO表空间大小应设置为计算结果的1.2~1.5倍,预留缓冲空间应对突发负载。
6.3 使用Undo Advisor
Oracle提供了Undo Advisor工具,可以帮助评估所需的UNDO表空间容量。通过查询V$UNDOSTAT视图的TUNED_UNDORETENTION列,可以了解系统自动调优后的UNDO保留时间。
七、DML操作UNDO产生量分析
7.1 总体原则
一般规律:
| DML操作 | UNDO产生量 | 原因 |
|---|---|---|
| INSERT | 最少 | 回滚INSERT只需要ROWID(行标识),相当于记录"删除这个ROWID" |
| UPDATE | 中等 | 需要存储被修改列的旧值,修改的列越多、数据越大,UNDO越多 |
| DELETE | 最多 | 需要存储整条记录的完整旧值 |
7.2 原理解析
从回滚的角度理解:
- 回滚INSERT → 执行DELETE(只需要ROWID即可定位并删除)
- 回滚DELETE → 执行INSERT(需要整条记录的完整数据才能重新插入)
- 回滚UPDATE → 执行反向UPDATE(需要记录被修改列的旧值)
7.3 索引的影响
索引的存在会显著改变UNDO的生成量:
- INSERT有索引的表:除了表数据外,每个索引的维护也会产生UNDO
- UPDATE非索引列:只产生表数据的UNDO,相对较少
- UPDATE索引列:需要记录索引列的旧值,UNDO量增加
- DELETE有索引的表:需要维护所有索引,产生大量UNDO
因此,UNDO的生成量取决于操作的数据量、表的索引数量以及具体修改的内容。
7.4 监控UNDO生成量
sql
-- 查看系统级UNDO生成量
SELECT name, value
FROM v$sysstat
WHERE name = 'undo change vector size';
-- 查看当前会话的UNDO生成量
SELECT name, value
FROM v$mystat m, v$statname s
WHERE m.statistic# = s.statistic#
AND s.name = 'undo change vector size';
八、释放UNDO表空间
重建UNDO表空间(推荐)
sql
-- 1. 创建新的UNDO表空间(设置合理大小)
CREATE UNDO TABLESPACE undotbs_new
DATAFILE '/oradata/orcl/undotbs_new01.dbf' SIZE 500M
AUTOEXTEND ON NEXT 100M MAXSIZE 10G;
-- 2. 切换到新的UNDO表空间
ALTER SYSTEM SET UNDO_TABLESPACE = undotbs_new;
-- 3. 确认切换完成
SHOW PARAMETER undo_tablespace;
-- 4. 等待一段时间,确保旧UNDO表空间不再被使用
可通过查询DBA_UNDO_EXTENTS确认无ACTIVE状态的区
SELECT COUNT(*) FROM dba_undo_extents
WHERE tablespace_name = 'UNDOTBS_OLD' AND status = 'ACTIVE';
-- 5. 删除旧的UNDO表空间
DROP TABLESPACE undotbs_old INCLUDING CONTENTS AND DATAFILES;
-- 示例,释放UNDOTBS3 表空间
SQL> SHOW PARAMETER undo_tablespace;
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
undo_tablespace string UNDOTBS3
SQL> CREATE UNDO TABLESPACE undotbs4
2 DATAFILE '/oradata/orcl/undotbs04.dbf' SIZE 50M
3 AUTOEXTEND ON NEXT 20M MAXSIZE 500M;
Tablespace created.
SQL> ALTER SYSTEM SET UNDO_TABLESPACE = 'UNDOTBS4';
System altered.
SQL> SELECT COUNT(*) FROM dba_undo_extents
2 WHERE tablespace_name = 'UNDOTBS3' AND status = 'ACTIVE';
COUNT(*)
----------
0
SQL> DROP TABLESPACE UNDOTBS3 INCLUDING CONTENTS AND DATAFILES;
Tablespace dropped.
九、常用查询
9.1 UNDO常用数据字典和视图
| 视图/字典 | 说明 |
|---|---|
V$UNDOSTAT |
监控UNDO表空间使用统计,每10分钟一条记录 |
V$TRANSACTION |
查看当前活跃事务及其占用的UNDO资源 |
V$ROLLSTAT |
UNDO段的状态统计信息 |
V$ROLLNAME |
UNDO段的名称 |
DBA_ROLLBACK_SEGS |
所有回滚段/UNDO段的信息 |
DBA_UNDO_EXTENTS |
UNDO表空间中区段(Extent)的详细信息,包含提交时间 |
DBA_TABLESPACES |
表空间基本信息(含UNDO类型)(是否开启GUARANTEE等) |
9.2 常用查询语句
查看UNDO表空间基本信息
sql
-- 查看所有UNDO表空间
SELECT tablespace_name, status, contents
FROM dba_tablespaces
WHERE contents = 'UNDO';
-- 查看各UNDO表空间的大小
select df.tablespace_name,round(sum(df.bytes)/1024/1024/1024,2) as "size(gb)"
from dba_data_files df
where tablespace_name in
(SELECT tablespace_name
FROM dba_tablespaces
WHERE contents = 'UNDO')
group by tablespace_name
order by 2 desc;
查看当前默认UNDO表空间
sql
SHOW PARAMETER undo_tablespace;
查看UNDO表空间使用情况
sql
--undo表空间实际使用情况 (推荐) 如果used_rag>60%需要查具体是哪个进程
--下面语句只查询ACTIVE undo段占用的使用率
set linesize 200
col used_pct format a8
select b.tablespace_name,
nvl(used_undo,0) "USED_UNDO(M)",
total_undo "Total_undo(M)",
trunc(nvl(used_undo,0) / total_undo * 100, 2) || '%' used_PCT
from (select nvl(sum(bytes / 1024 / 1024), 0) used_undo, tablespace_name
from dba_undo_extents
where status = 'ACTIVE'
group by tablespace_name) a,
(select tablespace_name, sum(bytes / 1024 / 1024) total_undo
from dba_data_files
where tablespace_name in
(select value
from v$spparameter
where name = 'undo_tablespace'
and (sid = (select instance_name from v$instance) or
sid = '*'))
group by tablespace_name) b
where a.tablespace_name (+)= b.tablespace_name
/
--下面语句只查询ACTIVE和UNEXPIRED undo段占用的使用率
select b.tablespace_name,
nvl(used_undo,0) "USED_UNDO(M)",
total_undo "Total_undo(M)",
trunc(nvl(used_undo,0) / total_undo * 100, 2) || '%' used_PCT
from (select nvl(sum(bytes / 1024 / 1024), 0) used_undo, tablespace_name
from dba_undo_extents
where status in ( 'ACTIVE','UNEXPIRED')
group by tablespace_name) a,
(select tablespace_name, sum(bytes / 1024 / 1024) total_undo
from dba_data_files
where tablespace_name in
(select value
from v$spparameter
where name = 'undo_tablespace'
and (sid = (select instance_name from v$instance) or
sid = '*'))
group by tablespace_name) b
where a.tablespace_name (+)= b.tablespace_name
/
查看UNDO表空间使用统计(V$UNDOSTAT)
sql
SELECT TO_CHAR(begin_time, 'YYYY-MM-DD HH24:MI') AS begin_time,
TO_CHAR(end_time, 'YYYY-MM-DD HH24:MI') AS end_time,
tuned_undoretention AS tuned_retention_sec,
maxquerylen AS max_query_length,
unxpstealcnt AS unexpired_steal_count,
expstealcnt AS expired_steal_count,
ssolderrcnt AS ora_01555_count,
nospaceerrcnt AS no_space_error_count
FROM v$undostat
ORDER BY end_time DESC;
关键字段说明:
tuned_undoretention:Oracle自动调优后的实际保留时间。
maxquerylen:统计周期内最长查询的执行时长。
unxpstealcnt:被迫覆盖未过期UNDO数据的次数,若大于0说明UNDO空间不足。
ssolderrcnt:ORA-01555错误出现次数。
nospaceerrcnt:ORA-30036错误出现次数。
查看UNDO表空间区段状态
sql
-- 查看UNDO表空间中各状态区段的大小
SELECT tablespace_name, status,
ROUND(SUM(bytes) / 1024 / 1024, 2) AS size_mb,
COUNT(*) AS extent_count
FROM dba_undo_extents
GROUP BY tablespace_name, status
ORDER BY tablespace_name, status;
查看当前活跃事务的UNDO使用情况
sql
-- 查看活跃事务占用的UNDO资源
SELECT r.name rbs,
nvl(s.username, 'None') oracle_user,
s.osuser client_user,
p.username unix_user,
s.sid,
s.serial#,
p.spid unix_pid,
t.used_ublk * TO_NUMBER(x.value) / 1024 / 1024 as undo_mb ,
TO_CHAR(s.logon_time, 'mm/dd/yy hh24:mi:ss') as login_time,
TO_CHAR(sysdate - (s.last_call_et) / 86400, 'mm/dd/yy hh24:mi:ss') as last_txn,
t.START_TIME transaction_starttime
FROM v$process p,
v$rollname r,
v$session s,
v$transaction t,
v$parameter x
WHERE s.taddr = t.addr
AND s.paddr = p.addr
AND r.usn = t.xidusn(+)
AND x.name = 'db_block_size'
ORDER by undo_mb desc
/