
1. ORA-01654 到底卡在哪一步ORA-01654: 索引 XXX 无法通过 128 (在表空间 XXX 中) 扩展这条报错我在生产库上处理过不下二十次几乎每次都伴随业务方的电话「订单提交不了」「批量任务卡住了」。它属于典型的空间类报错跟 ORA-01653表无法扩展、ORA-01652临时段无法扩展是同一个家族区别只在于卡住的对象不一样——这次是索引段在伸手要空间表空间没给。先把结论摆前面这条报错不是数据库坏了而是索引所在的段segment在插入、更新、分裂过程中需要申请一个新的区extent表空间里凑不出这么一整块连续的、大小符合要求的空闲空间于是一次分配动作失败前端语句直接抛错中断。数字128后面省略了单位实际是128KB代表段头记录里的下一区大小next extent。这个数字看着小恰恰说明问题不一定出在「表空间总量不够」而很可能是碎片或者文件的扩展上限被顶住了。适合谁看这个问题日常要接 DBA 工单的应用运维、刚接手 Oracle 库的后端开发、以及需要自己兜底小公司数据库的同学。本文按「搞清机制 → 定位定性 → 选方案 → 完整实操 → 避坑」的顺序走最后给出的 SQL 都可以直接贴到 SQL*Plus 或任意客户端里跑。1.1 报错文本里的四个信息位这条报错格式固定逐字段拆开看每一段都对应一个决策点。索引 XXX这里会带上 schema 前缀形如APP.IDX_ORDER_CUST。第一件事就是确认这个索引属于哪张表、什么类型普通 B 树、唯一索引、位图索引、函数索引、分区索引的某个分区因为不同类型处置手法不一样。位图索引重建代价和普通索引差很远分区索引的局部索引可以只重建单个分区。无法通过 128这是请求分配的新区大小单位 KB。为什么偏偏是 128两种典型来源——表空间用了UNIFORM SIZE 128K的本地管理方式所有区都整齐地是 128KB或者这是个从老版本升上来的字典管理表空间索引建的时候带了STORAGE (NEXT 128K)。如果是AUTOALLOCATE的本地管理表空间段小于 1MB 时区大小是 64KB超过 1MB 后变 1MB所以报错里的数字通常是 64、1024、8192、65536 这一档出现 128 反而不多见。看到 128基本可以反推表空间的区管理方式。在表空间 XXX 中锁定战场。所有后续的空间查询、扩容操作都围绕这个表空间展开。隐含的第四个信息是「时间点」——报错开始出现的时刻。这个信息报错本身不带得靠告警日志alert log和业务反馈拼。如果是凌晨批量跑批时集中出现多半是数据量增长顶到了水位如果是全天零散出现碎片或者文件达到 MAXSIZE 的可能性更大。注意不要一看到 ORA-01654 就去加数据文件。我见过不少案例表空间总量还有几十 G纯粹是连续空闲块凑不出来加文件虽然能临时缓解但碎片问题会持续恶化几周后同样的报错卷土重来。1.2 索引段扩张的物理过程与 next_extent要理解这条报错得先明白索引是怎么「长大」的。Oracle 的索引是一棵 B 树叶子块里存的是「键值 rowid」的排序结果。当你往表里插数据对应的索引叶子块会逐渐填满达到PCTFREE阈值后发生块分裂split老块里的数据被分一半到新块里——这就要向表空间申请新的数据块。分配不是一块一块要的Oracle 以「区」为单位整块申请。段的头部segment header维护着NEXT_EXTENT这个属性告诉系统「我下次要多大的一块」。申请流程是段头提出需求 → 表空间找一块大小足够、连续的空闲区 → 找到就分配段的区数量 1找不到就报 ORA-01654。关键就在「连续」两个字。表空间层面的空闲空间统计是按区extent粒度记录的DBA_FREE_SPACE里每一行就是一个空闲区。一个表空间可能合计有 3GB 空闲但分成八百个互不相邻的小块最大的一块只有 512KB。这时候索引张口要 1MB系统找不到 1MB 的连续块直接拒绝。这就是碎片型 ORA-01654 的本质。在AUTOALLOCATE的本地管理表空间里Oracle 按段的当前大小阶梯式决定区大小段小于 1MB 时每区 64KB1MB 到 64MB 之间每区 1MB64MB 到 1GB 之间每区 8MB超过 1GB 后每区 64MB。所以大索引的NEXT_EXTENT往往已经涨到 64MB对连续空间的要求很苛刻碎片一旦形成就容易出事——这也是为什么这类报错在数据量大的库上更常见。1.3 和邻近几个 ORA 错误的分工很多人把这几个错误混为一谈其实各有分工判断错了方向就会白忙活。错误码受影响对象本质原因典型处置ORA-01653表table段表空间给不出需要的区扩表空间、治碎片ORA-01654索引index段同上只是对象是索引扩表空间、重建索引、迁移ORA-01652临时段排序、哈希、全局临时表需要 temp 空间扩临时表空间、优化 SQLORA-01688回滚段 / UNDOundo 表空间不足扩 undo、调整 undo 保留策略ORA-01950用户配额用户在该表空间的配额用尽调 quota 或给 UNLIMITEDORA-01631表的最大区数MAXEXTENTS限制调大 maxextents本地管理已少见这张表值得贴在工位上。同一个表空间报 ORA-01653 和 ORA-01654 是常事因为表和索引混放但如果只报 01654 而表插入正常说明索引单独占了一个紧张的表空间这在「表一个表空间、索引另一个表空间」的规范部署里非常常见——索引表空间往往扩容不及时成了短板。2. 先定性再动手三组查询把问题锁死处置之前必须先判断「到底是哪种类型的空间不足」这是整个流程里最容易被跳过、也最容易出错的环节。凭经验乱加文件轻则浪费空间重则掩盖了真正的问题。我习惯用三组查询快速定性整个过程两分钟内能跑完。2.1 第一组表空间总账与数据文件水位先看这个表空间有几个文件、多大、能不能自动扩、扩到哪为止。SELECT file_id, file_name, ROUND(bytes/1024/1024, 2) AS 当前MB, autoextensible AS 自动扩展, ROUND(maxbytes/1024/1024, 2) AS 上限MB, ROUND(increment_by * 8192 / 1024 / 1024, 2) AS 每次扩MB, status, online_status FROM dba_data_files WHERE tablespace_name TS_BIZ ORDER BY file_id;这里有个经常被忽略的点INCREMENT_BY的单位是数据块不是字节也不是 MB所以必须乘以块大小默认 8KB才能换算成 MB。上面这条 SQL 里我直接乘了 8192如果你的库块大小是 16KB 或 32KB要改成对应值用SHOW PARAMETER db_block_size确认一下。重点看两个数AUTOEXTENSIBLENO的文件bytes就是它的天花板扩不动了就得RESIZE或者加新文件AUTOEXTENSIBLEYES的文件要看当前MB是否已经贴住上限MB如果两者相等说明自动扩展已经失效这时候表面上看它是「自动扩展的」实际上已经满了特别容易误判。我就吃过这个亏查autoextensible是 YES 就放心了结果bytes和maxbytes一模一样白折腾半小时。再看文件总量SELECT tablespace_name, ROUND(SUM(bytes)/1024/1024, 2) AS 总大小MB, ROUND(SUM(GREATEST(maxbytes - bytes, 0))/1024/1024, 2) AS 还能自动扩MB FROM dba_data_files GROUP BY tablespace_name ORDER BY 总大小MB DESC;「还能自动扩 MB」这一列如果接近 0说明这个表空间的自动扩展能力已经耗尽只能手工介入。顺带在主机上跑一下df -h确认文件系统还有空间——数据库层面允许扩操作系统没地方写一样会失败而且报错信息会变得很隐晦。2.2 第二组目标索引段的家底与下一区大小接下来聚焦报错的那个索引。SELECT i.owner, i.index_name, i.table_name, i.index_type, i.status, i.tablespace_name, ROUND(i.initial_extent/1024, 2) AS 初始区KB, ROUND(i.next_extent/1024, 2) AS 下一区KB, ROUND(s.bytes/1024/1024, 2) AS 段大小MB, s.extents AS 区数量, s.blocks AS 占用块数 FROM dba_indexes i JOIN dba_segments s ON i.owner s.owner AND i.index_name s.segment_name WHERE i.index_name IDX_ORDER_CUST;下一区KB就是报错文本里那个数字的来源对照一下是不是 128 或 65536 之类。区数量和段大小MB能看出段的体量一个 50MB 的索引和 5GB 的索引处置策略完全不同前者直接重建成本很低后者得挑窗口。想更精确地拿到「下一次分配到底要多大」用DBMS_SPACE包最稳它直接读段头不依赖统计信息新鲜度SET SERVEROUTPUT ON DECLARE v_total_blocks NUMBER; v_total_bytes NUMBER; v_unused_blocks NUMBER; v_unused_bytes NUMBER; v_fid NUMBER; v_bid NUMBER; v_bno NUMBER; BEGIN DBMS_SPACE.UNUSED_SPACE( segment_owner APP, segment_name IDX_ORDER_CUST, segment_type INDEX, total_blocks v_total_blocks, total_bytes v_total_bytes, unused_blocks v_unused_blocks, unused_bytes v_unused_bytes, last_used_extent_file_id v_fid, last_used_extent_block_id v_bid, last_used_block v_bno); DBMS_OUTPUT.PUT_LINE(段总字节: || v_total_bytes); DBMS_OUTPUT.PUT_LINE(未使用字节: || v_unused_bytes); END; /顺带提醒一个坑DBA_INDEXES.NEXT_EXTENT这个字段在某些版本上对分区索引、特别是全局索引的显示可能不准确只有主段信息。分区索引出问题时要去DBA_IND_PARTITIONS里逐个分区看别被主表的空值误导。2.3 第三组碎片度与连续可用块这是定性的核心。SELECT tablespace_name, COUNT(*) AS 空闲块数, ROUND(SUM(bytes)/1024/1024, 2) AS 空闲合计MB, ROUND(MAX(bytes)/1024/1024, 2) AS 最大连续MB, ROUND(AVG(bytes)/1024, 2) AS 平均块KB FROM dba_free_space WHERE tablespace_name TS_BIZ GROUP BY tablespace_name;看「最大连续MB」。如果它小于索引的下一区大小那根因就确认了——不是没空间是空间碎了。举例空闲合计 1.5GB最大连续 32MB而索引下一区 64MB必然失败。这时候往表空间里加一个 4GB 的数据文件能马上缓解因为新文件的空闲空间是完整的一整块但如果不解决碎片几个月后又会在新文件上重现。还要注意DBA_FREE_SPACE的粒度是区不是块所以「最大连续」实际上是「最大的那个空闲区」它代表了单次能分配的上限。另外本地管理表空间的位图里如果相邻空闲区能被合并Oracle 会自动合并——COALESCE的作用就在这儿。顺便查一下DBA_TABLESPACE_USAGE_METRICS拿整体水位这个视图在 10g 之后才有且只对本地管理表空间准确SELECT tablespace_name, ROUND(used_space * 8192 / 1024 / 1024, 2) AS 已用MB, ROUND(tablespace_size * 8192 / 1024 / 1024, 2) AS 总MB, ROUND(used_space * 100 / tablespace_size, 2) AS 使用率 FROM dba_tablespace_usage_metrics WHERE tablespace_name TS_BIZ;2.4 一张判别表定方向三组查询跑完把结果往这张表里套方向基本就出来了。现象组合判定首选动作总大小 ≈ 已用文件无自动扩展真满加数据文件 / RESIZE有自动扩展但 bytes maxbytes扩展上限顶住调大 MAXSIZE 或 RESIZE空闲合计充足最大连续 下一区碎片加文件应急 重建/迁移治本空闲充足文件也未满仍报错用户配额或段属性异常查 dba_ts_quotas、查 maxextents只在跑批时段报错增长型压力评估增长率后扩容报错对象是临时表上的索引检查临时表空间扩 temp、查 dba_temp_free_space「用户配额」这一行容易被漏掉。虽然配额不足通常报 ORA-01950但在某些场景下比如通过 definers rights 过程间接插入错误可能被包装转化。查一下配额不费事SELECT username, tablespace_name, ROUND(bytes/1024/1024, 2) AS 已用MB, ROUND(max_bytes/1024/1024, 2) AS 配额MB FROM dba_ts_quotas WHERE tablespace_name TS_BIZ;max_bytes -1表示无限制UNLIMITED TABLESPACE。看到具体数字就要留意了。3. 六种处置手段及其适用边界方案没有优劣只有匹配。下面这几种我按「应急 → 治本 → 预防」的顺序排实际处置时经常是组合拳。3.1 扩表空间加文件与 RESIZE 的参数怎么算最直接的手段。加文件ALTER TABLESPACE TS_BIZ ADD DATAFILE /u01/app/oracle/oradata/ORCL/ts_biz04.dbf SIZE 4G AUTOEXTEND ON NEXT 512M MAXSIZE 30G;SIZE 4G是初始大小不要给太小否则刚建完又要扩NEXT 512M是每次自动扩展的增量我一般设成 256M 到 1G 之间。设太小比如 64M会导致频繁触发扩展扩展动作本身要短暂持有文件级锁批量写入时会有感知设太大则在文件系统上瞬间吃掉大片空间留给其他库的余量不好控。MAXSIZE要结合文件系统剩余空间和单文件理论上限一起算。8KB 块的小文件表空间单个数据文件的理论上限是 4194304 个块 × 8KB ≈32GB大文件表空间bigfile则是 4G 个块理论能到 32TB。所以给普通表空间设MAXSIZE UNLIMITED其实是一种误导实际到 32GB 就封顶了。稳妥做法是把 MAXSIZE 设在 30GB 以内留出余量或者干脆用大文件表空间规划大表。扩已有文件ALTER DATABASE DATAFILE /u01/app/oracle/oradata/ORCL/ts_biz01.dbf RESIZE 8G;RESIZE只能往大了改不能小于当前已用空间会报 ORA-03297。改之前必须确认文件系统有余量这条在测试环境无所谓在生产环境改废了只能从备份恢复属于高危操作。扩容大小怎么定别拍脑袋。看一眼索引所在表的近期增长假设这个索引每周增长 2GB上一次扩容在 3 周前那么合理余量是「4 到 6 周的增量」也就是 8GB 到 12GB。再留 20% 的碎片缓冲最终给 12GB 到 15GB 比较踏实。这个算法没有任何玄学就是「增长率 × 观察窗口 安全系数」。注意如果表空间是AUTOALLOCATE且已经严重碎片化新加的文件虽然能解决眼下的报错但由于新文件会优先被使用很快又会被写碎。加完文件后一定要安排一次碎片治理别把它当成一劳永逸。3.2 治碎片REBUILD、SHRINK、COALESCE 怎么选这三个命令都能改变索引段的物理分布但作用机理完全不同用错了没效果还会白耗资源。ALTER INDEX ... COALESCE做的是「合并同一区内相邻的空闲块」不解锁新的连续空间。它只对字典管理表空间或者UNIFORM分配的老式表空间有明显效果对AUTOALLOCATE的索引基本是聊胜于无。执行代价低、不阻塞 DML只短暂锁段头可以作为低峰期的常规维护但别指望靠它解决 ORA-01654。ALTER INDEX APP.IDX_ORDER_CUST COALESCE;ALTER INDEX ... SHRINK SPACE是把段尾部的高水位线下移把腾出来的空间还给表空间效果立竿见影。但它有两个硬性前提表空间必须启用 ASSM自动段空间管理也就是SEGMENT SPACE MANAGEMENT AUTO索引不能是某些特殊类型比如基于函数的位图索引组合等。执行时建议分两步先COMPACT在线做再短暂锁住做SHRINK SPACE把对业务的影响压到最小。-- 第一步在线整理业务基本无感 ALTER INDEX APP.IDX_ORDER_CUST SHRINK SPACE COMPACT; -- 第二步正式回收 HWM短暂持锁 ALTER INDEX APP.IDX_ORDER_CUST SHRINK SPACE;ALTER INDEX ... REBUILD是最彻底的方案整段重建逻辑上等同于「新建一个索引 删掉旧的」。它天然解决了碎片问题还能顺带更新统计信息、改变存储属性。代价也最大需要一个与索引大小相当的空间放新段还要有足够的排序空间PGA 不够就落盘到临时表空间以及对应的 UNDO 来支撑回滚。ALTER INDEX APP.IDX_ORDER_CUST REBUILD ONLINE TABLESPACE TS_IDX PARALLEL 4 COMPUTE STATISTICS;关于参数的选择ONLINE允许重建期间继续 DML但代价是维护一张日志表重建末尾要「回放」总耗时可能翻倍NOLOGGING能显著减少 REDO 生成加速重建但重建完成后的第一次 DML 仍会补写 REDO且在 DataGuard 环境下可能导致备库块损坏因为 NOLOGGING 操作不传日志主备架构下要慎用PARALLEL 4加速但有 CPU 和 IO 代价而且结束后往往需要把并行度降回来否则后续 DML 也会以并行方式跑反而更慢。ALTER INDEX APP.IDX_ORDER_CUST NOPARALLEL;这一句经常被忘。执行完并行重建不降并行度索引的DEGREE一直挂着后续单条插入可能变成并行执行计划属于典型的「埋雷」。3.3 挪位置把索引迁到宽松表空间如果原表空间短期内没法清理比如还混着大量业务表把索引挪走是性价比最高的做法。ALTER INDEX APP.IDX_ORDER_CUST REBUILD TABLESPACE TS_IDX;一句命令同时完成「重建」和「迁移」重建期间原表空间只承担旧段的读取新段的分配全部落在目标表空间。前提是目标表空间的空间要算够新段大小 ≈ 原段大小 × (1 PCTFREE 调整带来的膨胀系数)一般按原大小的 1.1 到 1.3 倍预留。遇到分区索引要分开处理。局部索引LOCAL可以逐分区搬业务影响小ALTER INDEX APP.IDX_ORD_PART REBUILD PARTITION P_2026_01 TABLESPACE TS_IDX;全局索引GLOBAL跟着表分区走需要整体重建或者配合UPDATE GLOBAL INDEXES操作。分区表做DROP PARTITION时如果不加这个子句全局索引会整段变成 UNUSABLE后续查询直接走全表扫描性能断崖式下跌——这个问题我见过太多次一定要在变更脚本里写死。ALTER TABLE APP.T_ORDER DROP PARTITION P_2025_01 UPDATE GLOBAL INDEXES;注意UPDATE GLOBAL INDEXES会让 DROP 操作变慢要同步维护索引但换来的是索引始终可用。凌晨窗口够长就用它窗口紧张可以拆成「DROP → 重建全局索引」两步但中途索引不可用要评估业务能否容忍。3.4 改属性next_extent、MAXSIZE、限额修改索引的STORAGE子句在本地管理表空间下绝大多数情况无效这是新手最容易踩的坑。执行ALTER INDEX ... STORAGE (NEXT 1M)系统可能不报错但实际分配行为仍由表空间的区管理策略决定NEXT_EXTENT该多大还是多大。只有在字典管理表空间里STORAGE子句才真正生效。所以看到「网上说改 STORAGE 就行」的文章先确认你的表空间管理方式SELECT tablespace_name, extent_management, allocation_type, segment_space_management FROM dba_tablespaces WHERE tablespace_name TS_BIZ;EXTENT_MANAGEMENT LOCAL且ALLOCATION_TYPE SYSTEM即 AUTOALLOCATE时改存储参数没意义ALLOCATION_TYPE UNIFORM时区大小固定也改不了只有DICTIONARY才是传统的那套规则。真正能改的是数据文件的MAXSIZEALTER DATABASE DATAFILE /u01/app/oracle/oradata/ORCL/ts_biz01.dbf AUTOEXTEND ON NEXT 512M MAXSIZE 20G;这条命令适合「文件已到上限但磁盘还有空间」的场景是成本最低的修复方式之一。执行前用df -h确认文件系统余量别让操作系统层面的空间写满那会引发比 ORA-01654 严重得多的问题。用户配额ALTER USER APP QUOTA UNLIMITED ON TS_BIZ; -- 或者给具体值 ALTER USER APP QUOTA 10G ON TS_BIZ;生产环境我倾向给具体值而不是 UNLIMITED原因很简单——限制一个失控的批处理任务把整个表空间撑爆比事后清理要省事得多。配额这层「保险丝」加不加取决于团队对上游代码的信任程度。3.5 减体积分区维护与历史数据瘦身有些表空间的紧张是历史数据堆出来的。订单表三年没清过索引自然跟着膨胀。这种情况下扩容只是把问题往后推真正的解法是归档。分区表用DROP PARTITION或TRUNCATE PARTITION最干脆注意配合全局索引维护见上一节。非分区表可以走「在线重定义」把表改造成分区表再用ALTER TABLE ... MOVE PARTITION把冷数据搬到低速表空间索引重建时自然落到不同的位置。-- 冷分区搬到历史表空间同时释放原表空间 ALTER TABLE APP.T_ORDER MOVE PARTITION P_2024_12 TABLESPACE TS_HIS; -- 之后必须重建该分区的索引否则索引处于 UNUSABLE ALTER INDEX APP.IDX_ORD_PART REBUILD PARTITION P_2024_12;MOVE PARTITION之后局部索引会失效这一点必须写进变更检查清单。至于清理策略保留多少个月、归档到哪、是否压缩存储属于业务决策技术上能做的就是把这些动作做成可重复执行的脚本别每次手工敲。4. 一次完整的现场处置记录模拟案例下面这个案例是把前面几节串起来的一个完整走位人物和库名都是代称数字做了调整但每一步的思路和真实场景一致。4.1 现象与第一手信息某日 09:12 开始业务系统陆续报「订单创建失败」。日志里刷屏的是ORA-01654: unable to extend index APP.IDX_ORDER_CUST by 65536 in tablespace TS_ORDER注意这里的数字是 65536即 64MB说明这个索引段已经超过 1GB处在 64MB 一区的档位。第一反应是「表空间满了」但先别急按流程走。第一组查询结果TS_ORDER共 4 个数据文件合计 64GB三个文件AUTOEXTENSIBLE YESMAXSIZE都已设 20GB且bytes maxbytes全部顶格第四个文件 4GBAUTOEXTENSIBLE NO第二组查询结果IDX_ORDER_CUST段大小 3.2GB区数量 51下一区 64MB。第三组查询结果空闲合计 1.8GB最大连续块仅 24MB。到这里结论很清楚了总量上 4GB 空闲64GB 总量减去已用但最大连续块 24MB凑不出 64MB属于碎片型 扩展上限顶格的复合问题。4.2 一次无效的尝试和它的原因现场有人第一时间执行了加数据文件ALTER TABLESPACE TS_ORDER ADD DATAFILE .../ts_order05.dbf SIZE 2G;执行成功但报错没停。原因很直接新文件虽然提供了 2GB 的完整连续空间看起来够了但索引是在原文件上「继续长」的——段的数据块并不要求全部落在同一个文件里新增的区会优先落到新文件理论上确实能缓解。问题是2GB 对一个 3.2GB 且在高速增长的索引来说太小两小时内又被吃干再次触发同样的报错。这个教训很典型应急扩容的尺寸必须覆盖「当前缺口 一段时间内的增长」不能只解决眼前那一次分配。如果当时直接给 16GB配合自动扩展至少能撑两周把窗口留到碎片治理上。4.3 分步处置确认根因后处置分三步走全程盯业务监控。第一步补齐自动扩展能力先稳住ALTER DATABASE DATAFILE .../ts_order01.dbf AUTOEXTEND ON NEXT 1G MAXSIZE 24G; ALTER DATABASE DATAFILE .../ts_order02.dbf AUTOEXTEND ON NEXT 1G MAXSIZE 24G; ALTER DATABASE DATAFILE .../ts_order03.dbf AUTOEXTEND ON NEXT 1G MAXSIZE 24G;把三个文件的 MAXSIZE 从 20GB 提到 24GB前提是文件系统还有 12GB 以上余量df -h显示余量 40GB安全。这一步立刻让报错停止因为 64MB 的连续空间请求可以由自动扩展补上。第二步重建索引碎片从根上解决-- 先确认临时表空间够不够重建大索引需要排序空间 SELECT tablespace_name, ROUND(tablespace_size * 8192 / 1024 / 1024, 2) AS 总MB, ROUND(free_space * 8192 / 1024 / 1024, 2) AS 空闲MB FROM dba_temp_free_space;临时表空间剩 18GB索引 3.2GB按 1.5 倍预留约 4.8GB空间充足。于是挑业务低谷执行ALTER INDEX APP.IDX_ORDER_CUST REBUILD ONLINE PARALLEL 4 COMPUTE STATISTICS; ALTER INDEX APP.IDX_ORDER_CUST NOPARALLEL;重建耗时 22 分钟其间业务正常读写CPU 峰值到 60%在可接受范围。重建后段大小从 3.2GB 降到 2.1GB区数量从 51 降到 34回收了约 1.1GB。第三步把散落的空闲空间收拢。重建后表空间里多出一些零散的 64MB 区块用DBA_FREE_SPACE复核SELECT COUNT(*) AS 空闲块数, ROUND(SUM(bytes)/1024/1024, 2) AS 合计MB, ROUND(MAX(bytes)/1024/1024, 2) AS 最大连续MB FROM dba_free_space WHERE tablespace_name TS_ORDER;最大连续块升到 3.2GB问题解除。4.4 回归验证处置之后要做三件事缺一不可。一是确认索引状态正常SELECT owner, index_name, status, tablespace_name, last_analyzed FROM dba_indexes WHERE index_name IDX_ORDER_CUST;STATUS必须是VALID。重建期间如果并发事务有冲突可能留下UNUSABLE一旦如此必须立刻再重建一次。二是确认统计信息已更新否则优化器可能仍按旧数据选错执行计划。COMPUTE STATISTICS会同步更新但如果用了NOLOGGING或者别的方式记得手工补EXEC DBMS_STATS.GATHER_INDEX_STATS(APP, IDX_ORDER_CUST);三是盯三天的业务指标。包括订单创建的成功率、该表的 DML 响应时间、表空间的日增量。第三项最关键用它反推下一次扩容的时点写进运维日历。5. 排查速查表与踩坑心得5.1 常见问题速查表现象可能原因验证方式处置报错数字是 128表空间为 UNIFORM 128K 或字典管理查dba_tablespaces.allocation_type加空间或迁移到 AUTOALLOCATE 表空间空闲合计大仍报错碎片最大连续块不足dba_free_space的 MAX(bytes)加文件应急 重建索引加文件后很快复发新文件被写碎 / 尺寸给太小观察dba_free_space变化一次给足配合碎片治理文件显示可自动扩但仍报错bytes 已到 maxbytes对比bytes与maxbytes调大 MAXSIZE 或 RESIZE只在夜间批处理时报错增长型压力白天空闲对比报错时间与批处理时间评估增量后扩容重建时报 ORA-01652临时表空间不足dba_temp_free_space扩 temp 或减少并行度重建后索引 UNUSABLE并发 DML 冲突dba_indexes.status重新执行 REBUILD报错对象是分区索引需定位到具体分区dba_ind_partitions.status单分区重建改 STORAGE 无效果本地管理表空间不认查extent_management改用扩容/迁移方案分区 DROP 后查询变慢全局索引失效dba_indexes.status重建或加 UPDATE GLOBAL INDEXES5.2 那些文档里不写的坑坑一AUTOEXTEND 的「假在线」。前面提过一次这里再强调——DBA_DATA_FILES.AUTOEXTENSIBLE显示 YES不代表还能扩。必须同时比对BYTES和MAXBYTES。我见过巡检脚本只查autoextensible就报「正常」结果库炸了才发现文件早到顶。巡检 SQL 里一定要加这个条件。坑二重建大索引的排序空间被低估。索引 10GB临时表空间只剩 8GBREBUILD跑到 90% 报 ORA-01652前面的时间全白费。经验值是按索引大小的 1.2 到 1.5 倍预留临时空间并且PARALLEL越高每个并行进程都要独立的排序区总需求会成倍上涨。并行度 4 的重建临时空间按 2 倍留都不算多。坑三ONLINE 重建的「尾部卡顿」。ONLINE模式下重建主体完成后要把期间捕获的 DML 日志回放到新索引上这段时间表现为「进度条不动」持续时间取决于重建时长和并发 DML 量。业务高峰期做这个操作回放阶段可能比主体还长而且会持有更重的锁。我的习惯是必须在业务低谷做别看它叫 ONLINE 就以为真的随时随地。坑四COALESCE被当成万能药。网上很多文章说「索引碎片用 COALESCE 就行」在 AUTOALLOCATE ASSM 的环境里它的效果微乎其微。判断标准很简单如果DBA_FREE_SPACE的最大连续块远小于下一区COALESCE 救不了你只有 REBUILD 或加文件。坑五忘记降并行度。前面写过这里再点一次。并行重建后索引的DEGREE会保留后续单条 INSERT 都可能走并行CPU 飙高、锁竞争加剧问题极难排查。坑六在备库/灾备环境忽略 NOLOGGING。NOLOGGING重建不产生完整 REDO主库重建后备库对应数据块可能损坏。主备架构下这个操作要么禁用要么重建后立刻做一次全备。这个坑不常遇到遇到一次就是事故。坑七RESIZE缩小文件。有些人想「先把文件缩小再加」释放文件系统空间。RESIZE小于当前 HWM 会报 ORA-03297而且缩小操作本身风险极高除非你明确知道高水位在哪。生产环境我基本不做缩小宁可加新文件。坑八只盯表空间不看文件系统。数据库层面所有检查都通过ADD DATAFILE执行时报操作系统错误因为/u01满了。每次扩容前跑一次df -h两秒钟的事能省掉大量来回。提示所有 DDL 类操作REBUILD、RESIZE、DROP PARTITION在 executing 之前先把执行计划、预估耗时、回滚方案写进变更单。这类操作一旦中断恢复成本远高于多做一次预演。5.3 日常监控把阈值报警做在前面ORA-01654 是可以被提前发现的那类错误前提是监控做到位。第一条监控表空间使用率SELECT tablespace_name, ROUND(used_space * 100 / NULLIF(tablespace_size, 0), 2) AS 使用率, ROUND((tablespace_size - used_space) * 8192 / 1024 / 1024, 2) AS 剩余MB FROM dba_tablespace_usage_metrics WHERE used_space * 100 / NULLIF(tablespace_size, 0) 80 ORDER BY 使用率 DESC;阈值定 80% 告警、90% 严重告警。注意这个视图算的是「已分配空间内的使用率」它不包含文件还能自动扩展的部分所以要配合第二条一起看。第二条监控自动扩展余量SELECT tablespace_name, ROUND(SUM(GREATEST(maxbytes - bytes, 0)) / 1024 / 1024, 2) AS 剩余可扩MB, COUNT(*) FILTER (WHERE autoextensible YES AND bytes maxbytes) AS 顶格文件数 FROM dba_data_files GROUP BY tablespace_name ORDER BY 剩余可扩MB;顶格文件数 0就要处理了哪怕当前使用率只有 70%因为增长是不可控的。FILTER子句是较新版本支持老版本改成SUM(CASE WHEN ... THEN 1 ELSE 0 END)。第三条监控碎片指数SELECT tablespace_name, ROUND(MAX(bytes) / 1024 / 1024, 2) AS 最大连续MB, ROUND(SUM(bytes) / 1024 / 1024, 2) AS 空闲合计MB, ROUND(MAX(bytes) * 100 / NULLIF(SUM(bytes), 0), 2) AS 连续度百分比 FROM dba_free_space GROUP BY tablespace_name HAVING SUM(bytes) 0 ORDER BY 连续度百分比;「连续度百分比」低于 10% 就该警惕了说明空闲空间极度分散。这个指标比单纯看使用率更能提前预警碎片型故障。把这三条做成每日巡检脚本结果推送到值班群配合一个「表空间增长趋势」的周报记录每周的表空间总量与增量扩容决策就有数据支撑不用等报错来催。我在实际维护中把这三条跑了两年多ORA-01654 从「突然爆发」变成了「提前一周就知道要加空间」处置节奏完全不一样。最后分享一个自己的习惯每次处理完这类空间问题我都会把这个表空间的「当前总量、周增量、本次扩容后的预计可用周数」记到一张维护台账里。这张台账价值极高——下次再报错时翻开就知道上次加到多少、撑了多久、增长斜率有没有变化。比起每次从零开始查效率差着数量级。