
表空间使用率告警是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 namedb_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秒。这就是最典型的“自动恢复”现象整个过程数据文件大小没变但文件内部的空间被临时段占满后又释放了。场景Bundo表空间缓慢回收。这个实验更贴近实际生产。我把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 namedb_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查segtypeSORT优化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多留一点现场数据远比频繁扩容更有价值。