☰
Oracle表空间使用率异常增长后自动恢复的根因分析
2026/10/22 10:39:38 网站建设 项目流程

表空间使用率告警是DBA最常见的一种夜间电话,大多数时候就是扩一下数据文件,加个定时任务监控就算完事了。但有一种比较邪门的情况:告警说表空间快满了,你火急火燎地登录上去,查了一遍等了一晚上,它自己又恢复正常了,使用率肉眼可见地往下降,数据文件也没动过,业务也没停过。这种“异常增长后又自动恢复”的现象,新手遇到容易一脸懵,老手如果处理不当,也会被表象带偏方向,最后排查半天找不到真凶。

本文就围绕这个场景做一次完整的故障模拟和分析,把这层“鬼打墙”式的表象拆开来看。核心思路是从临时段、撤销段、回收站、段空间延迟分配这几个方向入手,结合具体SQL和真实实验过程,说清楚为什么出现这种先涨后跌的曲线,以及到底该怎么查、怎么防。适合刚上手Oracle运维的同学,也给有几年经验但对这类隐性增长原理还比较模糊的同行做个参考。

1. 故障现场与初步表象

先说模拟环境。我用的是Oracle 19c单实例,部署在虚拟机里,undo表空间固定5GB,临时表空间固定10GB,业务表空间用户数据约2TB。为了模拟真实告警,我写了一个简单的循环插入程序,模拟业务高峰期大量事务同时开启,每个事务更新若干行大字段数据,不做提交,人为制造会话长期占用undo的情况。同时另开一个会话,跑一个涉及大批量排序的查询,把临时表空间也用起来。

先看当时的告警快照,表空间监测平台显示数据表空间使用率从正常值一路冲到96%,但过了大概40分钟又回落到72%,而且占用最高的时间点前后,会话数和活跃事务数都没有明显异常波动。从趋势图上能清楚看到一条尖峰状曲线:快速拉升,保持一段高位,然后迅速回落,整个过程没有人为干预,也没有新增数据文件或者调整任何配置。

这种曲线和普通的一次性写入导致的持续增长完全不同。普通写入之后使用率是平滑上升,不会自动回到原来的水平,除非删数据或者缩容。而“尖峰状”增长背后,必然存在某种临时占用的对象在特定条件下被释放。根据我以往的经验,第一时间锁定了两个嫌疑最大的区域:临时表空间和undo表空间,但也顺手查了回收站和段空间的状态,避免主观漏判。

先看整体空间占用。

select tablespace_name, sum(bytes/1024/1024) total_mb, sum(decode(maxbytes,0,bytes,maxbytes)/1024/1024) max_mb, sum(user_bytes/1024/1024) used_mb from dba_data_files group by tablespace_name;

这个SQL只能看到数据文件的物理大小,看不到内部对象的变化规律。想复现异常增长并追踪恢复过程,真正要盯的是dba_segments和v$temp_space_header这类动态视图。不过在上手这些视图之前,先补一下基础认知:到底是什么东西会让表空间使用率先涨后跌,这两类“幽灵增长”最常见的来源是什么。

2. 异常增长又恢复的常见成因拆解

先给结论,这类“自动恢复”的表空间增长,99%不是数据真的写进去了,而是有会话在某个时间段内临时占用了大量空间,结束后这些占用又被释放了。按出现频率排序,主要就四个方向。

第一是临时表空间的排序、哈希、临时结果集占用。排查这类问题的时候,会看到两张比较典型的视图:v$sort_usage显示了当前正在使用临时段的会话,dba_temp_free_space反映临时表空间可用空间。如果某个报表SQL或者分页查询,在PGA不足的时候把排序放到临时表空间里执行,一次大排序就可能吃光几十个GB的临时空间。SQL跑完或者超时被干掉之后,临时段立刻释放,使用率自然回落。

第二是undo表空间的自动扩展与收缩。事务开启后,修改前的镜像数据要写入undo段。如果业务高峰期出现了大量并发更新并且长时间不提交,undo表空间会持续增长。更常见的是,undo_retention设置了比较大,即使事务结束,旧版本数据也要保留一段时间用于一致性读和闪回查询,所以undo数据文件在业务高峰后会继续维持高水位,直到过了保留期,空间才慢慢回收。这部分增长通常在几小时或一天后逐步下降,和临时表空间那种“秒级回落”又有差别。

第三是被忽略的回收站(Recyclebin)。如果在某个时间段内,有人执行了drop table或者truncate table,并且表比较大,那么这些段会以BIN$开头的名字留在回收站里,外层看来表空间使用率一下子就涨上去了。回收站里的对象不释放空间,直到你手动purge recyclebin,或者被空间压力触发自动清理。如果后续有新的插入操作触发了空间压力,Oracle的自动清理机制可能会把部分回收站对象彻底清掉,然后使用率又降下来,容易给人一种“自己恢复了”的错觉。

第四是段空间延迟分配和本地管理表空间的特性。从Oracle 11g开始,默认建表时空闲段不会立即分配物理空间,而是在插入第一批数据时才真正分配extent。这在某些批量生成数据的场景下,会出现表空间使用率和表大小不完全同步、“跳跃式增长”的情况。不过这更多是解释“为什么增长得如此突然”,不能直接解释“为什么自动恢复”,所以诊断优先级靠后。

还有一种偏冷门的情况:expdp数据泵导出或者外部表查询在加载过程中使用了较大的位图映射区或者临时LOB段,操作结束后这些段被清理。这类问题同样表现为尖峰曲线,但占比不高。

针对本文的模拟,我重点复现的是前两种,顺便把回收站场景也拉进来做对照。这样测试结论更有区分度,实际排查时也能按照这个思路逐个排除。

3. 故障复现与模拟实验

为了把整个“异常增长后又恢复”的过程完整记录下来,我分三个场景做了实验。

场景A:临时表空间尖峰。我先把临时表空间调成固定1GB,模拟空间紧张的情况。然后在会话1里跑一条笛卡尔乘积查询,直接对两张百万级表做无条件的join,强制排序和哈希连接都落到临时段。执行的同时,每隔10秒查一次v$sort_usage和dba_temp_free_space。

select a.tablespace_name, b.segtype, b.blocks * (select value/1024/1024 from v$parameter where name='db_block_size') as seg_mb from v$sort_usage b, dba_tablespaces a where b.tablespace_id = a.tablespace_id;

执行到30秒左右,临时表空间使用率到了98%,会话的sid/serial#出现在v$sort_usage里,segtype是SORT。我把这个查询正常跑完,再看临时表空间使用率直接回落到1%以下,时间不到5秒。这就是最典型的“自动恢复”现象,整个过程数据文件大小没变,但文件内部的空间被临时段占满后又释放了。

场景B:undo表空间缓慢回收。这个实验更贴近实际生产。我把undo_retention设置为900秒,也就是15分钟。然后在会话里开启一个事务,更新一张有4000万行记录的表,每次更新100万行但不提交,用脚本循环操作。事务保持期间,观察undo表空间的数据文件使用,可以看到已分配空间持续增长,SQL如下。

select tablespace_name, status, sum(bytes/1024/1024) frag_mb from dba_undo_extents group by tablespace_name, status;

在我的模拟环境里,事务持续了12分钟,undo使用率从20%涨到87%。事务正常提交之后,我继续观察,发现使用率并没有立刻掉下来,因为旧undo信息还要服务一致性读和闪回查询。过了大概18分钟,使用率逐步回落到30%左右。这种恢复曲线是阶梯式下滑,和场景A的陡降明显不同。

场景C:回收站触发自动清理。这个实验我单独造了一张5GB的表,数据量比较大,然后对它执行drop操作。这张表进了回收站之后,我又往其他表里持续插入数据,直到表空间使用率超过85%,触发自动维护任务对回收站对象做清理。一段时间后,使用率出现下降,但回收站里部分对象被清掉明显是被空间压力“挤出去”的。

三个场景跑完,能清楚地看出“异常增长”虽然表象一致,但恢复的时间特征、内部机制完全不同。只看曲线不够,必须深入到段级别的动态视图和等待事件,才能准确区分。

4. 核心诊断方法与决定性SQL

基于实验过程,我整理了排查这类问题的完整手段。先按照“先段后文件、先动态后静态”的顺序走,可以避免被数据文件大小误导,也能显著缩短定位时间。

第一步,确认增长的方向。如果告警发生在业务高峰,优先查临时段和undo段;如果发生在夜间批处理或开发人员执行DDL之后,优先查回收站。判断依据很简单:增长到回落如果发生在几分钟以内,大概率是临时表空间;如果持续几小时甚至一晚,优先怀疑undo;如果和drop/truncate操作时间点吻合,回收站是首选。

第二步,查看当前所有表空间的增长历史。Oracle的DBA_HIST_TBSPACE_USAGE_HISTORY可以返回近期的采样快照,不需要提前部署其他插件,直接在维护窗口查即可。

select rt.begin_time, rt.end_time, t.tablespace_name, rt.tablespace_size * t.block_size / 1024 / 1024 as size_mb, rt.tablespace_usedsize * t.block_size / 1024 / 1024 as used_mb from dba_hist_tbspc_usage_his rt, dba_tablespaces t where rt.tablespace_id = t.tablespace_id and rt.begin_time > sysdate - 1 order by rt.begin_time;

从这张表能看出,问题表空间的使用率在什么时间段扎堆上涨,并且和系统负载曲线对应起来。如果历史采样粒度不够细,再结合awr报告看TOP事件和TOP SQL。

第三步,对临时表空间做专项判断。找到具体会话是关键,查询v$session里sql_id对应执行计划中的TEMP空间估算,也比看整体使用率准得多。

select s.sid, s.serial#, s.username, s.status, u.segtype, u.tablespace, u.blocks * (select value from v$parameter where name='db_block_size') / 1024 / 1024 as temp_used_mb, q.sql_id, q.disk_reads from v$session s, v$sort_usage u, v$sqlstats q where s.saddr = u.session_addr and s.sql_id = q.sql_id order by temp_used_mb desc;

这个SQL,再加上v$tempseg_usage,基本可以锁定是哪类SQL在啃临时空间。另外可以跑一句比较实用的诊断:

select tablespace_name, sum(bytes_used) used_bytes, sum(bytes_free) free_bytes from v$temp_space_header group by tablespace_name;

如果used_bytes很小,但文件大小很大,说明临时文件内部存在“空洞”,这种状态类似高水位问题,需要收缩临时文件来释放物理空间。

第四步,对undo空间做精细分析。关注dba_undo_extents里status为ACTIVE的区,如果大量undo段处于ACTIVE状态,说明有长事务占着旧镜像不放手,即使redo日志不写了,空间也一直悬着。

select status, count(*), sum(bytes/1024/1024) mb from dba_undo_extents group by status;

另一个关键指标是,v$undostat里的maxquerylen,代表当前最长的一条查询执行时间。如果这个值接近undo_retention,说明undo保留期设置可能偏大,导致空间回收滞后。

第五步,检查回收站。这步最简单,也不容易漏:

select owner, object_name, type, space as space_blocks, time from dba_recyclebin order by space desc;

看到大量BIN$开头的对象,且占用空间很大,就可以确定掉坑里了。回收站的清理触发条件和表空间压力有关,缺省自动清理也比较慢,这也是为什么使用率“过一会儿自己下来了”。

5. 根因判定与处理对策

结合模拟实验结果,我把这类问题归结为三类根因,每一类都有对应的处理策略。

第一类根因是SQL性能问题导致临时空间被大量占用。这种问题最直接的方案就是优化SQL,减少排序和哈希连接的大小,把大结果集分批处理,或者调整PGA参数pga_aggregate_target,尽量让排序在内存中完成而不下盘。但参数调整要谨慎,PGA给得太大会造成内存竞争,在小内存主机上容易触发swap。经验做法是先找到罪魁SQL,看执行计划的TEMP_SPACE估算,再确认是不是缺少Join条件或者统计信息过期导致优化器选错执行计划。固定了SQL之后,临时表空间的使用率通常能明显回落。

第二类根因是undo_retention和undo表空间大小设置不合理。如果undo表空间是自动扩展的,并且每次峰值都会被撑大,说明undo_retention相对业务的事务时长偏长,或者undo文件太小导致提前进入extension。处理上不建议把undo_retention调得很小来“节省空间”,因为那会影响报表查询的闪回和一致读能力。正确做法是先量化业务长事务的平均时长以及最长查询时间,把undo_retention设置成覆盖峰值成熟时间,再通过undo表空间的自动扩展配置加上监控阈值来实现弹性。模拟里,我把undo_retention从900秒降到600秒后,回落时间明显缩短。

第三类根因是回收站对象未及时清理。这个最简单,但也最容易被人忽略。运维规范里应该增加一个定时任务,每天业务低峰期执行purge dba_recyclebin。不过要注意,purge也会产生undo和redo,最好在维护窗口做。如果对bin空间非常敏感,可以在建表时直接指定purge子句,让Drop之后不进回收站。生产环境建议至少保留24小时的回收站数据,便于紧急恢复,但一定要有配套的监控和清理任务。

结合我的模拟过程,最终的判定流程是这样的:先看历史趋势确定问题时间窗,然后抓那个时间窗内的活跃会话和TOP SQL,再根据恢复特征确定属于哪种空间,然后做专项SQL验证。整个过程大概半小时足够,比盲目扩数据文件更稳妥。很多“自动恢复”其实是系统在帮你擦屁股,问题被掩盖了,但下一次高峰还会再来,根因不除,告警会周而复始。

对“扩数据文件”这件事,我建议不到万不得已不要做。尤其是临时表空间和undo表空间,扩容只是在掩盖SQL和参数问题。空间够用的情况下,扩容只会把高水位继续垫高,后续维护负担更大。

6. 常见问题速查与避坑经验

把模拟过程中踩过的坑和常见疑问整理一下,做成速查表,方便同行以后直接用。

表象最可能原因快速验证处理思路
使用率几分钟内冲顶又快速回落临时表空间大排序/哈希v$tempseg_usage查segtype=SORT优化SQL,控制并行度
使用率高峰持续几小时后阶梯下降undo保留期未过v$undostat看maxquerylen调整undo_retention或扩容
使用率涨上去就不怎么降正常业务数据增长查dba_segments,对比增长段扩容或清理历史数据
使用率涨后下降但存在大量BIN$对象回收站自动清理查dba_recyclebin规划purge任务
表空间使用率显示很高但实际可用空间多高水位问题查segments/block数收缩段或重建表

再补充两个我实际遇到过的坑。

坑一,只盯数据文件大小,不看使用对象。有一次同事说表空间恢复正常了,没有处置。我查了一下,确实“正常”了,数据文件大小没变,但里面的空间空出来很多,这时候就要考虑是否回收站触发了自动清理。并不是说不用写报告,而是得彻底定位是什么对象导致的尖峰,否则下次同样场景还会发生。

坑二,开着自动扩展就一直放任不管。自动扩展是双刃剑,带来空间弹性的同时,也可能让数据文件在峰值时被撑得很大,之后很难缩回来。数据文件缩容很费劲,尤其是大于32GB的文件,还得考虑是否支持在线resize。实操中我习惯把自动扩展的上限(maxsize)约束在一个可控范围,宁可让告警触发,也不要让文件无限膨胀。

最后分享一个小技巧,临时表空间的高水位回收可以这样操作:先保证当前没有活跃排序会话,然后alter tablespace temp shrink space keep 1G,指定保留1GB,把高水位降下来。undo表空间shrink相对麻烦,需要切换undo表空间,一般不建议在线做,除非空间告警持续存在且无法通过参数调整解决。

我个人在实际排查中的体会是,表空间异常增长“自动恢复”是一种典型的隐性故障,它不像文件满导致业务停摆那么紧急,却往往预示更严重的SQL或配置问题。每次遇到“自己好了”的故障,都不能轻易归档,多跑一条SQL,多留一点现场数据,远比频繁扩容更有价值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询