别看临时表空间名字里带个"临时",真要被占满的时候,它带来的麻烦一点都不临时。我印象最深的一次是某个结算日的凌晨,值班的同学说数据库告警、批量任务全卡住,等我连上去看,DBA_TEMP_FREE_SPACE里的空闲空间已经只剩几个 G,V$TEMPSEG_USAGE上挂着十几个会话,每个都在啃几十 G 的临时段。业务侧的表现很有意思——不是连接不上,而是原本几秒的报表查询变成几十分钟,插入语句报ORA-01652: unable to extend temp segment。这就是临时表空间被占满的典型画像:数据库没死,但到处都在"喘不上气"。这篇东西我想把这件事从根上讲透:临时表空间在 Oracle 里到底承担什么角色、占用怎么定位到具体会话和 SQL、应急阶段怎么在最小代价下把业务救回来、事后怎么从 SQL 和参数两个层面做根治,以及我在实际处理中踩过的那些坑。不管你是刚接手数据库运维的新手,还是已经在写复杂 SQL 的开发,只要你的系统里有大表关联、批量入库、报表统计这类活儿,迟早都会遇到它。
1. 先把临时表空间的角色捋清楚
1.1 临时表空间不是"缓存",它是磁盘上的兜底工作台
很多人对 temp 的第一误解是把它当成某种缓冲区。其实临时表空间和 undo、redo 一样,是实打实的磁盘文件(tempfile),只不过里面存放的"临时段"在会话结束或者操作完成后会被回收。它的核心用途只有一个:当内存装不下某个操作的中间结果时,把这些中间结果落到磁盘。
具体哪些操作会产生临时段?我按经验从高频到低频列一下:ORDER BY排序、GROUP BY聚合、DISTINCT去重、UNION去重(注意UNION ALL不会)、哈希连接(HASH JOIN)的构建侧溢出、位图索引的创建与合并、索引创建与重建时的排序、CREATE TABLE AS SELECT、全局临时表的数据存储、以及统计信息收集和逻辑导出时的内部排序。
这里要区分一个关键概念:临时段分会话级临时段和事务级临时段。会话级临时段的数据在整个会话周期内都有效,比如全局临时表ON COMMIT PRESERVE ROWS里的数据;事务级临时段在事务提交后就失效,典型的例子就是排序。Oracle 官方文档里把这个差别讲得很清楚,但实际影响在于——事务级临时段的占用会随着一个长事务里多次排序不断叠加,因为它明明已经没用了,却因为事务没结束而不能被其他会话复用,这也是"临时表空间越用越满、但杀了会话就好了"的根本原因。
注意:临时段上的操作几乎不产生 redo(只有极少量的字典变更会),所以你不能指望从 redo 日志里找回临时段里丢了什么。这既是好事(性能),也意味着临时段里的数据没有任何恢复价值,回收它不会影响数据一致性。
1.2 "占满"有两种,一种是空间真没了,一种是没法扩展
我在排查时习惯先把"占满"分成两类,因为处理路径完全不同。
第一类是空间真的用光了。DBA_TEMP_FREE_SPACE显示 free_space 很小,新来的排序操作申请不到空间,报ORA-01652。第二类其实更隐蔽——表空间里还有空闲 extent,但临时文件本身到达了MAXBYTES上限,或者所在磁盘满了,导致 tempfile 无法 autoextend。这两种情况下应用报的错一模一样,但你去看DBA_TEMP_FREE_SPACE结果可能天差地别。
还有一种第三类情况值得单独说:临时表空间组的成员表空间容量不一致。如果你用了临时表空间组(TEMP GROUP),默认分配策略是 UNIFORM,Oracle 会把请求均匀打到各个成员上。这时候如果某个成员的表空间比别的小,它会先满,而整体看起来"还有空间"。我遇到过组里两个表空间一个 32G 一个 8G,结果 8G 那个天天报错。
判断到底属于哪一类,一条语句基本够用:
SELECT TABLESPACE_NAME, TABLESPACE_SIZE/1024/1024 AS TOTAL_MB, ALLOCATED_SPACE/1024/1024 AS ALLOC_MB, FREE_SPACE/1024/1024 AS FREE_MB FROM DBA_TEMP_FREE_SPACE;DBA_TEMP_FREE_SPACE是 10g 之后替代V$TEMP_SPACE_HEADER的视图(后者在 12c 之后已经废弃),它给出的ALLOCATED_SPACE是当前已经分配给临时段的容量,FREE_SPACE是可再分配的容量。我个人的经验阈值是:免费空间长期低于总容量 20%,就该考虑扩容或优化了;如果ALLOCATED_SPACE接近TABLESPACE_SIZE,说明已经踩到天花板。
1.3 它为什么总在"业务高峰"被占满
临时表空间被占满往往不是持续的,而是集中爆发。原因在于临时段是"申请-释放"模式:一个报表 SQL 无论占用 50G 还是 1G,跑完就释放。所以你会看到一个奇特的现象——白天一切正常,晚上跑批的时候炸。
这背后有两个推手。一个是并发度。单个大查询占 10G 不可怕,十个同样的查询同时跑就是 100G,而临时段的分配很"贪心",它不会因为还有空闲空间就少申请,每个会话都在按自己的执行计划需要去要。另一个是执行计划漂移。同一句 SQL,统计信息过期后优化器可能从走索引改成全表扫 + 哈希连接,临时空间需求瞬间翻几倍。我见过最夸张的一句统计 SQL,因为一个谓词条件从等值改成了范围,执行计划从 INDEX RANGE SCAN 变成 HASH JOIN + SORT,临时空间从 200M 直接飙到 40G。
实操心得:如果临时表空间的占用曲线呈现出明显的尖峰形态,而不是缓慢爬升,那基本可以断定是某几类批量 SQL 或某个并发高峰导致的,排查方向应该放在"哪个时段、哪些 SQL",而不是去查"谁忘了提交事务"。
2. 定位:到底是谁在吃临时表空间
2.1 三张视图配合使用,才能锁到源头
定位占用最常用的视图是V$TEMPSEG_USAGE,它本质上是V$SORT_USAGE的替代(V$SORT_USAGE在 12c 之后已经不推荐使用)。这个视图给出的字段不多但够用:SESSION_ADDR、SESSION_NUM、SERIAL#、SQLADDR、SQLHASH、TABLESPACE、SEGTYPE、SEGFILE#、SEGBLK#、BLOCKS。
注意这里的BLOCKS是块数,乘上DB_BLOCK_SIZE才是实际占用。我见过有人直接报"某会话占用 400 万",其实是 400 万块,换算下来 30G 左右。搞清楚单位能少开很多无效会。
一条我常用的组合查询,直接定位到会话和 SQL:
SELECT s.SID, s.SERIAL#, s.USERNAME, s.PROGRAM, s.MACHINE, s.STATUS, u.TABLESPACE, u.SEGTYPE, u.BLOCKS * (SELECT VALUE FROM V$PARAMETER WHERE NAME='db_block_size')/1024/1024/1024 AS GB, q.SQL_ID, q.PLAN_HASH_VALUE, SUBSTR(q.SQL_TEXT,1,120) AS SQL_SNIPPET FROM V$TEMPSEG_USAGE u JOIN V$SESSION s ON s.SADDR = u.SESSION_ADDR LEFT JOIN V$SQL q ON q.SQL_ID = s.SQL_ID ORDER BY u.BLOCKS DESC;几个字段的实际意义:SEGTYPE能告诉你这临时段是干什么的——SORT是排序,DATA通常对应全局临时表或索引创建,HASH是哈希连接溢出,LOB_DATA是 LOB 的临时存储。看到大量HASH,八成是某个大表关联的哈希连接在建构建侧;看到DATA集中在少数会话上,多半是全局临时表被当普通表用了。
PLAN_HASH_VALUE我特意留在查询里,因为同一个SQL_ID可能有多个执行计划,如果同一句 SQL 的临时占用忽大忽小,对比计划哈希值往往是突破口。
2.2 空间被占满了,但视图里一个会话都没有
这是最容易让人慌神的场景:告警说临时表空间满了,你查V$TEMPSEG_USAGE却是空的。原因通常有这几类,我按遇到频率排序。
第一类是SMON 还没清理完。会话被 kill 或者异常断开之后,它占用的临时段要由 SMON 异步回收,这个过程有延迟,几秒到几分钟都有可能。如果连上去的时候刚刚 kill 过会话,稍等一会儿再看通常就正常了。判断方法是看V$SORT_SEGMENT里的FREE_REQUESTS,如果它一直是 0 而空间还在被占,说明没有新的申请在排队,正在等回收。
第二类是多实例共享同一个临时表空间。RAC 环境下V$TEMPSEG_USAGE只反映当前实例,另一个实例上的会话在上面查出来是看不到的。这种情况要查GV$TEMPSEG_USAGE。我第一次在 RAC 上遇到时愣了十几分钟,后来才想起来要加 G。
第三类是临时表空间里有"残留"的碎片。临时表空间是本地管理(LOCALLY MANAGED)的,正常情况下会自动管理 extent,但在极少数版本上,如果 session 非正常终止而 SMON 又没处理干净,会留下无法回收的段。这时候DBA_TEMP_FREE_SPACE显示占用很高但找不到持有者,通常只能重建临时表空间。
第四类是临时文件被外部进程占用。比如 OFS 或者某些备份工具打开了 tempfile 还没释放,从数据库内部看就是空间没回来。
注意:查不到占用会话时,不要急着杀会话或重建表空间。先确认是不是 RAC 跨实例、是不是刚做过 kill、是不是 SMON 还没刷完。我见过有人上来就重建临时表空间,结果把正在跑批的任务全部打断,复盘时发现只是 SMON 晚了几分钟。
2.3 从 SQL 文本和计划里读出"为什么这么费空间"
定位到 SQL_ID 只是第一步。真正要解决,得看它的执行计划为什么需要这么多临时空间。方法很简单:
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('&sql_id', NULL, 'ALLSTATS LAST'));重点看三处。第一处是执行计划里的TEMP TABLE TRANSFORMATION、SORT ORDER BY、HASH JOIN、SORT UNIQUE这些算子,它们就是临时空间的"消费者"。第二处是A-Rows和Buffers的对比,如果某个哈希连接的A-Rows是几百万行,那它的构建侧肯定要溢出到临时表空间。第三处是PDBLK或者显示的临时空间统计,有些版本的DBMS_XPLAN会直接在计划里标出每一步的临时空间使用量。
我一般的判断逻辑是:如果大临时空间来自HASH JOIN,优化方向是把驱动表变小、把连接条件上的索引补起来、或者调大 PGA 让哈希表能留在内存里;如果来自SORT ORDER BY,方向是看能不能用索引避免排序,或者缩小参与排序的行数;如果来自SORT UNIQUE(去重),那就考虑UNION改UNION ALL、DISTINCT改成GROUP BY加过滤、或者干脆改成EXISTS语义。
3. 应急:先让业务恢复,再考虑优雅
3.1 扩容还是杀会话,取决于这是"持续需求"还是"异常占用"
应急阶段最容易做错的决定,就是在没搞清楚状况的时候直接扩临时表空间。扩容确实最快,一条命令就行:
ALTER TABLESPACE TEMP ADD TEMPFILE '/u01/oradata/temp02.dbf' SIZE 10G AUTOEXTEND ON NEXT 1G MAXSIZE 32G;但如果你扩了 30G,过两天又满了,说明问题不在容量而在用法。我见过最典型的一例,临时表空间从 32G 扩到 128G,一周后照样满,最后查出来是一个全局临时表ON COMMIT PRESERVE ROWS的会话开完不关,连接池又复用这些连接,数据越堆越多。这种情况下扩容只是延缓事故。
判断依据其实很简单:看占用是不是集中在少数几个会话上,且这些会话的 SQL 是否"合理"。如果是一句统计报表的 SQL 占了 50G,而它本来就该占这么多,那是容量规划问题,扩容合理。如果是 20 个会话各占 2G 但都是同一句跑了很久的批量 SQL 在并发,那要考虑限制并发。如果是某个会话占了 80G 而它的 SQL 平淡无奇,那大概率是计划漂移,杀了以后重新收集统计信息更划算。
真要杀会话,命令是:
ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;IMMEDIATE这个关键字很关键。不加它,Oracle 只是标记会话为 killed,等它自己发现再退出,可能等到天荒地老;加了它,会话会被立刻中断、事务回滚。代价是回滚本身可能很耗时——如果这个会话正在做大量 DML,回滚时间可能比它已经跑的时间还长。所以在杀之前,看一眼会话的STATE和LAST_CALL_ET,如果它正在COMMIT或者已经跑了很多小时,杀掉之前要想清楚回滚成本。
实操心得:杀会话前先记下
SQL_ID和PLAN_HASH_VALUE,这是事后复盘的唯一线索。我有一次急急忙忙杀掉会话,事后想追查为什么它会占 40G,结果会话一断V$SQL里的执行计划还在,但SQL_ID没记,只能从 AWR 里翻,多花了半小时。
3.2 临时表空间快照式重建:能解决大部分"顽固"占用
当临时表空间里存在无法回收的残留段,或者你想调整表空间大小、把临时文件挪个位置,重建是最干净的办法。但这里有个坑:默认临时表空间不能直接 drop。
正确顺序是新建一个、切换默认、再删旧的:
-- 1. 建一个新的临时表空间(用 bigfile 还是 smallfile 看现有规划) CREATE TEMPORARY TABLESPACE TEMP_NEW TEMPFILE '/u01/oradata/temp_new01.dbf' SIZE 20G AUTOEXTEND ON NEXT 2G MAXSIZE 64G EXTENT MANAGEMENT LOCAL UNIFORM SIZE 1M; -- 2. 切换数据库默认临时表空间 ALTER DATABASE DEFAULT TEMPORARY TABLESPACE TEMP_NEW; -- 3. 把用户级的临时表空间也切过去(如果有) -- 先把现有的查出来 SELECT USERNAME, TEMPORARY_TABLESPACE FROM DBA_USERS WHERE TEMPORARY_TABLESPACE = 'TEMP'; -- 然后逐个 ALTER USER xxx TEMPORARY TABLESPACE TEMP_NEW; -- 4. 等旧表空间上的会话全部退出后再删除 DROP TABLESPACE TEMP INCLUDING CONTENTS AND DATAFILES;第 4 步是真正的难点。只要还有任何一个会话在旧表空间上有临时段,DROP就会报ORA-12952: The request exceeds the maximum allowed database size或者ORA-14404之类的错误。这时要看V$SESSION里有没有还挂着旧临时表空间的会话,通常是长连接池里的会话。稳妥做法是先切换默认,然后让应用侧连接池重启一轮,或者从数据库侧对应用账号做一次ALTER SYSTEM DISCONNECT SESSION。这一步我在生产上从来不敢图快,一般会挑一个业务低峰,并且提前和应用侧对齐"会有一次重连"。
3.3 临时表空间组:多实例和多租户下的稳妥选择
如果你的环境是 RAC 或者需要给不同业务分配不同的临时空间,我一般推荐用临时表空间组而不是单个表空间:
ALTER TABLESPACE TEMP_NEW TABLESPACE GROUP TEMPGRP; ALTER TABLESPACE TEMP_OLD TABLESPACE GROUP TEMPGRP; ALTER DATABASE DEFAULT TEMPORARY TABLESPACE TEMPGRP;组的好处是按 UNIFORM 分配,各个成员均摊负载。但它有个必须留意的前提:组内各成员容量要接近。前面提过,容量不均会导致小块先满,而整体看起来还行。所以用组的时候,我会定期跑一次检查,看各成员的TABLESPACE_SIZE差异是否超过 20%。
4. 根因拆解:五类最典型的"吃 temp 大户"
4.1 大表排序与哈希连接:最常见的两类
排序和哈希连接是临时空间的两大主力。区别在于,排序的需求量约等于参与排序的行数乘以每行平均长度(含排序键和需要带回的列),哈希连接的需求量约等于构建侧(通常是较小的那张表)过滤后的实际行数乘以行宽。
举个例子,一张 5000 万行的订单表和一个 200 万行的客户表做关联,如果用哈希连接,构建侧是客户表,200 万行 × 每行 300 字节 ≈ 600MB,这在 PGA 里放得下就没问题。但如果优化器选错了构建侧,拿订单表当构建侧,那就是 5000 万 × 300 字节 ≈ 15GB,必然溢出到 temp。
这类问题的优化思路有三条:一是让连接条件用上索引,避免走哈希;二是如果必须哈希,确保构建侧是小表,可以通过加提示(/*+ LEADING(小表) USE_HASH(大表) */)或者收集准确的统计信息来引导;三是调整 PGA,但这有个前提——PGA 内存在操作系统层面是预留的,不能无脑调大。
我做过一个粗略的估算公式,用于判断"该不该调 PGA":如果单会话临时占用经常在峰值时超过PGA_AGGREGATE_TARGET × 20%,说明 PGA 不够分了,可以试着调大PGA_AGGREGATE_TARGET,但一定要同步关注V$PGASTAT里的over allocation count,如果这个值在持续增长,说明 PGA 内部已经在超配,调大总量才有意义。
4.2 索引创建与在线 DDL:容易被忽略的短时高峰
CREATE INDEX和ALTER INDEX REBUILD在排序阶段会占用临时段,而且这个占用很"陡"——它不像报表 SQL 那样慢慢爬升,而是一开始就申请一大块。如果是REBUILD ONLINE,还要额外的临时空间来存放变更日志。
这类操作的坑在于它常常在业务时间执行。我见过运维在上午十点给一张 8000 万行的表加索引,结果临时表空间瞬间被吃掉一半,同时段的报表全部超时。正确的做法是把这类 DDL 放到窗口期,并且提前估算所需空间:排序所需空间大致等于索引键列的总长度 × 行数 × 1.2(排序过程的额外开销)。
另一个隐蔽的点是并行度。如果你用PARALLEL 8建索引,每个并行从属进程都会各自申请临时段,总占用可能是单进程的 8 倍。所以调高并行度不只是加速,也是在扩大临时空间需求。
4.3 全局临时表:从"临时"变成"常驻"的典型误用
全局临时表(GTT)的设计初衷是给会话存点中间数据,但我在实际项目里见过太多把它当普通表用的场景。尤其是ON COMMIT PRESERVE ROWS这种定义:数据在事务提交后还留着,只有会话断开才清。而如果应用用的是连接池,连接长期不释放,这些数据就越堆越多,最终把 temp 撑爆。
判断方法很直接:在V$TEMPSEG_USAGE里看SEGTYPE为DATA的占用,然后去DBA_TABLES里查这个表是不是TEMPORARY类型:
SELECT TABLE_NAME, TEMPORARY, DURATION, OWNER FROM DBA_TABLES WHERE TEMPORARY = 'Y' AND OWNER NOT IN ('SYS','SYSTEM');DURATION字段是关键——SYS$SESSION表示数据保留到会话结束,SYS$TRANSACTION表示事务提交即清。如果你发现某个 GTT 是SYS$SESSION且占用巨大,改造成SYS$TRANSACTION或者在使用后主动TRUNCATE往往能立竿见影。
注意:
DURATION可以在不重建表的情况下改,ALTER TABLE xxx MODIFY ...不支持直接改 DURATION,通常需要重建表或者通过ON COMMIT DELETE ROWS的语义调整。实际改造前务必确认应用逻辑,有的业务确实依赖跨事务保留数据。
4.4 游标不关、连接池配置不当带来的累积效应
这类问题最"温水煮青蛙"。表现是临时表空间占用缓慢上升,一段时间后达到临界值。根因往往在应用侧:连接池的最大连接数设置得很小(比如 20),但同时并发很高,导致每个连接被反复复用,而每次复用时前面会话的临时段还没来得及被 SMON 回收,被新请求复用后继续申请。
另一个常见来源是OPEN CURSOR超过OPEN_CURSORS参数,有些框架在异常分支里漏了close,游标长期挂着,它的排序区也就一直不释放。
排查这类问题,我会看V$SESSTAT里session cursor cache hits和opened cursors current的比值,如果opened cursors current长期接近OPEN_CURSORS,那基本能确认是游标泄漏,方向转到应用侧修代码比在数据库侧调参数有效得多。
4.5 统计信息收集与逻辑导出:夜间的隐形杀手
DBMS_STATS收集统计信息时,如果启用了ESTIMATE_PERCENT => DBMS_STATS.AUTO_SAMPLE_SIZE之外的策略,或者对分区表做全局统计,会在内部做大量排序和聚合,占用临时空间。而这类任务通常在凌晨跑,和跑批任务撞车。
逻辑导出(expdp)如果带QUERY参数,或者导出过程涉及排序,也会消耗 temp。我在一次夜间故障里发现,expdp的一个导出任务占了 30G 临时空间,而它在文档里几乎不会提到这件事,因为它是由内部的排序操作间接引起的。
针对这两类,我的建议是错峰:统计信息收集和逻辑导出不要和核心跑批挤在同一个时间窗。另外,对分区表可以按分区收集,把单次所需的排序规模降下来。
5. 参数与容量:从"救火"转向"可控"
5.1 PGA 与 temp 不是孤立的两件事
很多人调优临时表空间只盯着 temp 本身,实际上它的上游是 PGA。Oracle 在自动 PGA 管理(PGA_AGGREGATE_TARGET)下,会把排序、哈希这类工作区尽量放在 PGA 内存里,只有当单个工作区超过_SORT_AREA_SIZE的上限,或者 PGA 总量吃紧时,才会溢出到临时表空间。
所以一个很实际的判断逻辑是:如果V$PGASTAT里的cache hit percentage长期低于 80%,说明大量操作已经溢出到磁盘,此时调大PGA_AGGREGATE_TARGET往往比扩临时表空间更治本。反过来,如果cache hit percentage已经在 95% 以上但临时空间还是被占满,那说明这些操作的规模本身就超出了任何合理内存,扩 temp 才是对的。
我这里放一张对照表,是我在实际排查时常用的决策依据:
| 现象 | 更可能的根因 | 优先动作 |
|---|---|---|
temp 占用尖峰,cache hit%低于 80% | PGA 不足导致溢出 | 调大PGA_AGGREGATE_TARGET |
temp 占用尖峰,cache hit%高于 95% | 单次操作规模过大 | 优化 SQL 或扩 temp |
| temp 占用缓慢爬升不下降 | GTT 或游标泄漏 | 查V$TEMPSEG_USAGE的SEGTYPE |
| 只有部分时段满 | 并发批处理撞车 | 调度错峰 |
| 扩了 temp 仍反复满 | 执行计划漂移或容量规划错 | 查 AWR 找 TOP SQL |
用表格对照能省不少来回试的时间。我自己的习惯是每次处理完一个临时表空间问题,都把这几个指标记一笔,时间长了就能对自家系统的"正常水位"心里有数。
5.2 临时表空间容量怎么估才靠谱
估算容量最可靠的办法不是算,而是看历史峰值。做法是从 AWR 里取Temporary Space Usage这一段,看过去一个月每天的峰值占用,取 95 分位数再乘 1.5 倍作为规划容量。原因是临时空间的占用是并发的、非累加的,简单按"最大单条 SQL × 并发数"算出来往往偏大很多,导致浪费。
如果你想自己算,一个粗略的参考是:单个大查询的排序空间 ≈ 返回行数 × 行宽 × 1.2;哈希连接的构建侧空间 ≈ 构建侧过滤后行数 × 行宽 × 1.3。然后把所有可能并发的重型操作加起来,再乘以一个 0.6 左右的折减系数,因为实际很少有所有重操作同时到达峰值。
但我要强调的是,这个算法只适合做初始规划。真正的容量一定是观察出来的,AWR 是最可靠的数据源。
5.3 监控怎么落地
我给临时表空间配的告警是两条线:一条是DBA_TEMP_FREE_SPACE里的FREE_SPACE / TABLESPACE_SIZE < 20%触发预警,另一条是出现ORA-01652直接告警。第二条实际上是被动的,但很多问题第一次暴露就是靠它。
更进一步的做法是采集V$TEMPSEG_USAGE的快照,做 TOP 会话的历史记录。我用一个简单的 PL/SQL 定时任务,每 5 分钟记一次占用超过 1G 的会话,落到一张监控表里。这样出问题时能直接看"过去 24 小时内占用最多的会话排前 20",比事后翻 AWR 快得多,而且能看到杀掉的那一瞬间还剩下什么。
6. 常见问题速查与踩坑记录
这一节我按实际被问到最多的顺序整理,附上我的处理做法。
| 问题表现 | 排查方向 | 处理动作 |
|---|---|---|
ORA-01652但V$TEMPSEG_USAGE为空 | SMON 正在回收 | 等 5-10 分钟再查一次 |
| 视图为空但空间确实被占 | RAC 跨实例 | 查GV$TEMPSEG_USAGE |
| 杀了会话空间没回来 | 事务回滚尚未完成 | 查V$TRANSACTION和V$SESSION.STATUS |
同一个SQL_ID占用忽大忽小 | 执行计划漂移 | 对比PLAN_HASH_VALUE,重新收集统计信息 |
| 某个 8G 的临时表空间总是先满 | 临时表空间组成员容量不均 | 调整成员容量或拆分表空间组 |
| 白天正常夜间炸 | 跑批任务并发或规模过大 | 查 AWR 的 TOP SQL 和时段,错峰调度 |
| 扩容后一周又满 | 使用模式问题而非容量问题 | 查 GTT、游标、计划漂移 |
再说几条我认为最有价值的避坑经验,都是花过代价换来的。
第一条,不要在业务高峰做ALTER TABLESPACE TEMP相关的 DDL。哪怕是看起来无风险的ADD TEMPFILE,在某些版本上会有短暂的字典锁,我遇到过在高峰加 tempfile 导致几秒钟内所有需要新临时段的操作排队。
第二条,重建临时表空间之前,先把当前默认临时表空间上挂着的用户全查一遍。DBA_USERS里的TEMPORARY_TABLESPACE字段不只是数据库默认,很多应用账号会被显式指定到某个临时表空间,切默认不会影响这些账号。这一点如果漏了,重建之后你会发现有些会话还在旧表空间上跑,DROP永远失败。
第三条,对KILL SESSION要有敬畏。杀掉一个跑了 4 小时的会话,回滚可能要跑 2 小时,在这期间业务可能一直在报错。所以能等就等,尤其是当事务量大且涉及大量 DML 的时候。判断标准是看V$SESSION.USED_UBLK,这个值反映了回滚需要处理的 undo 块数,数值很大就说明回滚代价高。
第四条,AWR 里的临时空间数据要单独留档。AWR 默认保留 8 天,很多临时空间问题一个月才复发一次,等你回头查的时候数据早没了。我会把每周的Temporary Space Usage段导出存下来,这个习惯帮我抓到过一次"每月的第一个工作日必然报错"的规律,最后定位是月度结算批处理的并发没控制好。
第五条,临时表空间的文件放在什么存储上很关键。它写的是"临时性"数据,但压力一点不小——高峰期它的 IO 可能是最忙的。如果它和在线日志或者数据文件挤在同一块盘上,会互相拖累。有条件的把它单独放到一个存储池,效果比调参数更直接。
最后说一个我最近才想明白的点。临时表空间被占满这件事,很多时候被当成一个"空间问题"去处理,但它本质上是一个"工作负载问题"。你扩多大的空间,都只是把临界点往后推;真正让它不再反复的,是搞清楚自家系统里哪几类操作在吃临时空间、它们在什么时段并发、执行计划是否稳定。这三件事摸清了,临时表空间的问题基本就变成可预期的了——你会提前知道什么时候该扩容,什么时候该拦下某个 SQL,什么时候该去查应用侧游标。我现在接手一个新系统,第一件事就是把过去一个月的V$TEMPSEG_USAGE历史拉出来看看 TOP 10,比读任何文档都直观。