一次UNDO 表空间 99%故障处理

客户的 CRM 系统早上报了一堆 ORA-30036(无法扩展回滚段),查了下 UNDO 表空间使用率 99.8%,但表空间文件已经 200GB 了,OS 层还有空间,为什么 Oracle 不自动扩展?因为 MAXSIZE 设了 200G。更诡异的是,DBA 手动 ALTER UNDO TABLESPACE ... SHRINK SPACE 也卡住了,SMON 进程 CPU 占用 100%,但半小时过去,UNDO 使用率一点没降。

6.1 UNDO 段里的"僵尸事务"

UNDO 表空间不收缩的根本原因,通常是某个回滚段(Rollback Segment)里还有活跃事务槽位。Oracle 的 UNDO 段管理采用循环使用策略:一个 extent 里的所有事务都提交后,这个 extent 才能被标记为 EXPIRED,然后 SMON 或者后台清理进程才能把它回收。但如果有一个事务"卡"在 ACTIVE 状态,即使它已经不跑任何 SQL 了,它占用的 extent 以及后续所有 extent(因为 UNDO 段是循环的)都不能被回收。我们那次查 X$KTUXE(事务表),发现有一个事务从三天前就开始,状态是 ACTIVE,但对应的会话早就断了(V$SESSION 里找不到)。这就是典型的"僵尸事务"。

6.2 僵尸事务是怎么产生的

后来查了应用日志,发现是一个 Java 连接池的 bug:应用端做了事务提交(connection.commit()),但提交后没有立即关闭连接,而是把连接扔回连接池。连接池有个空闲检测机制,60 秒后把连接关掉,但关掉时只发了 TCP FIN,没发 Oracle 的 DISCONNECT 包。Oracle 端认为会话还在,事务状态保持 ACTIVE,直到 PMON 发现会话死了才清理。但 PMON 的清理周期默认是 10 分钟,如果僵尸事务太多,PMON 也忙不过来。

6.3 排查与清理

-- 查看 UNDO 段占用详情
SELECT tablespace_name,
       segment_name,
       status,
       sum(bytes)/1024/1024/1024 gb
FROM dba_undo_extents
GROUP BY tablespace_name, segment_name, status;
-- 发现 _SYSSMU1$ 有 180GB 都是 ACTIVE

-- 查活跃事务(关键视图)
SELECT ktuxesiz, ktuxesta, ktuxesql, ktuxecfl, ktuxesid
FROM x$ktuxe
WHERE ktuxecfl = 'DEAD' AND ktuxesta = 'ACTIVE';
-- ktuxecfl='DEAD' 表示会话已死,ktuxesta='ACTIVE' 表示事务还挂着

-- 强制回滚死事务(慎用,大事务可能跑很久)
-- 先查事务使用的 UNDO 块数
SELECT used_ublk FROM v$transaction WHERE addr = (SELECT ktuxeusn||'.'||ktuxeslt FROM x$ktuxe WHERE ...);
-- 如果 used_ublk > 1000000(约 8GB UNDO),强制回滚可能要几小时

-- 更快的方法:直接 shrink 特定回滚段
ALTER ROLLBACK SEGMENT "_SYSSMU1$" SHRINK TO 100M;
-- 但如果有活跃事务,这条命令会报错 ORA-30025

-- 终极手段:新建 UNDO 表空间,切过去,旧的慢慢等 SMON 清理
CREATE UNDO TABLESPACE undotbs2 DATAFILE '/u01/undotbs2.dbf' SIZE 50G AUTOEXTEND ON;
ALTER SYSTEM SET UNDO_TABLESPACE = undotbs2;
-- 旧 UNDO 表空间在事务全部过期后,可以 drop

我们那次用了终极手段:新建 UNDO 表空间切过去,因为那个僵尸事务占用了 180GB UNDO,强制回滚估计要 4 小时,业务等不起。切完 5 分钟后,所有新事务恢复正常。旧的 UNDO 表空间挂在那跑了两天,SMON 才慢慢把僵尸事务的 extent 清完。

事后给客户的建议:第一,连接池必须配置合理的空闲超时和连接验证(validation query),确保连接关闭时 Oracle 端能感知;第二,TUNED_UNDORETENTION 虽然能自动调整保留期,但在 UNDO 紧张时它会拼命拉长保留期,反而加剧空间压力,建议固定 UNDO_RETENTION = 900(15 分钟),别让它自动调;第三,每周跑一遍检查僵尸事务的脚本,发现 X$KTUXE 里有 DEAD+ACTIVE 状态立即处理。


请使用浏览器的分享功能分享到微信等