
1. 先认识这个报错ORA-01654到底在说什么1.1 报错现场还原先说个最常见的场景下午四点半开发同事甩过来一张截图说报表跑不出来了日志里躺着一行红字——ORA-01654: unable to extend index USR_ORDER_IDX by 128 in tablespace TBS_DATA第一次碰到的朋友很容易慌以为是索引坏了或者表坏了。其实都不是。这行报错翻译成人话就是TBS_DATA 这个表空间已经没有足够的空闲空间来继续分配给索引 USR_ORDER_IDX 了。这里的 128 单位是数据块Oracle Block意思是系统想再给这个索引段分配 128 个块的空间结果发现表空间里挤不出来了。这类报错在 Oracle 日常运维里出现频率非常高。不管是 11g、12c 还是 19c只要表空间规划没跟上数据增长速度迟早都会撞上它。适合谁看主要是初、中级 DBA以及那些需要自己维护 Oracle 库的开发人员。看完之后你能知道怎么快速定位是哪个表空间满了、怎么应急处理、怎么避免下次再犯。1.2 段、表空间与区间的存储机制要真正处理这个报错得先搞明白 Oracle 的存储层级。Oracle 的逻辑存储结构依次是表空间Tablespace→ 段Segment→ 区间Extent→ 数据块Block。表、索引、回滚段这些对象在 Oracle 里统称为段。每个段刚开始只有几个区间数据不断写入后段就需要申请更多区间来容纳新数据。每次申请区间时Oracle 会去所在表空间的空闲空间列表中找足够大的空间块。如果找来找去都凑不出一个区间所需的连续空间就会报 ORA-01654。这里有个容易误解的点表空间满了不等于磁盘满了。表空间是由一个或多个数据文件组成的。虽然服务器磁盘还剩好多 GB但如果表空间里的数据文件已经涨到了上限MAXSIZE或者数据文件关闭了自动扩展那这个表空间对 Oracle 来说就是满了。就像你的手机存储卡还有 64G但某个 App 被设置了 2G 的使用配额配额用完就提示空间不足其实卡里还有大把地方。另外一个关键概念是连续空间。如果你在表空间里看到空闲总量有 300MB但每个空闲碎片只有不到 1MB恰好你要分配的区间需要 8MB 连续块Oracle 依然会给你报错。这个问题放到后面避坑实录部分细说。1.3 别搞混ORA-01650 到 ORA-01656 这一家子ORA-01654 不是孤立的它周边有一大堆类似的报错基本上一家人整整齐齐。我列个表方便你一眼对应上报错编号报错含义常见触发对象ORA-01650无法扩展回滚段回滚段老版本 RBSORA-01651无法按指定数量扩展回滚段回滚段ORA-01652无法扩展临时段临时表空间、排序段ORA-01653无法扩展表堆表、分区表ORA-01654无法扩展索引索引、分区索引ORA-01655无法扩展聚簇聚簇表ORA-01656无法扩展 LOB 段LOB 字段的存储段ORA-01658无法在表空间中创建初始区间新建对象注意的是上面这些报错在处理思路上一脉相承都是表空间空间分配失败。区别只是段类型不同。本文重点讲 ORA-01654但后面给出的排查 SQL 和解决办法这套思路可以原封不动套到 ORA-01653 甚至临时表空间的 ORA-01652 上。2. 排查定位别急着加文件先搞清楚是谁满了2.1 从报错信息里读出关键字段ORA-01654 报错信息里其实包含了三个关键信息段名、扩展块数、表空间名。ORA-01654: unable to extend index USR_ORDER_IDX by 128 in tablespace TBS_DATA逐词拆解一下index USR_ORDER_IDX出问题的段类型是索引名字叫 USR_ORDER_IDX。by 128想继续扩展 128 个数据块。tablespace TBS_DATA这个索引所在的表空间叫 TBS_DATA。拿到这三条信息就可以动手查了。千万别跳过这一步直接加数据文件因为有些时候表空间的空闲量其实是够的真正的问题是编号规则、块大小或碎片导致的分配失败直接加文件也能解决但会掩盖真实问题。2.2 查表空间使用率第一件事确认 TBS_DATA 的整体使用情况。我最常用的查询是这个SELECT d.tablespace_name, ROUND(SUM(d.bytes) / 1024 / 1024 / 1024, 2) AS total_gb, ROUND(SUM(d.bytes - NVL(f.bytes, 0)) / 1024 / 1024 / 1024, 2) AS used_gb, ROUND(NVL(SUM(f.bytes), 0) / 1024 / 1024 / 1024, 2) AS free_gb, ROUND((1 - NVL(SUM(f.bytes), 0) / SUM(d.bytes)) * 100, 2) AS used_pct FROM dba_data_files d LEFT JOIN (SELECT tablespace_name, SUM(bytes) bytes FROM dba_free_space GROUP BY tablespace_name) f ON d.tablespace_name f.tablespace_name WHERE d.tablespace_name TBS_DATA GROUP BY d.tablespace_name;执行之后的结果一般能一眼看出问题如果 used_pct 超过 97%那基本就是空间耗尽如果 used_pct 只有 70%free_gb 也有好几个 G那就得换个角度查看是不是数据文件的可扩展上限问题。2.3 查数据文件状态和自动扩展配置表空间使用率高时数据文件很可能已经膨胀到了上限。这条 SQL 看每个数据文件的当前大小、最大大小和是否开启自动扩展SELECT FILE_ID, FILE_NAME, TABLESPACE_NAME, ROUND(BYTES / 1024 / 1024, 2) AS size_mb, ROUND(MAXBYTES / 1024 / 1024, 2) AS max_mb, AUTOEXTENSIBLE, INCREMENT_BY FROM dba_data_files WHERE TABLESPACE_NAME TBS_DATA ORDER BY FILE_ID;重点看 AUTOEXTENSIBLE 这一列如果是NO那数据文件大小是固定的用满就报 ORA-01654。如果是YES但当前大小已经等于 MAXBYTES说明到达了自动扩展上限同样报错。如果是YES还没到上限那就要考虑是不是磁盘本身满了或者数据文件数量已经超出了某个限制。这里补一句单数据文件并不是无限大的。Oracle smallfile 表空间下单个数据文件的块数上限是 4,194,3042 的 22 次方个块。如果数据库块是 8KB那单文件最大就是 32GB如果是 16KB 块单文件最大 64GB。所以很多上了规模的生产库表空间里都会挂七八个甚至十几个数据文件而不是靠一个文件无限涨。2.4 顺着报错找到具体对象拿到索引名字后最好再确认一下它属于哪张表以及它的表空间有没有被不小心改过SELECT OWNER, TABLE_NAME, TABLESPACE_NAME, STATUS FROM dba_indexes WHERE INDEX_NAME USR_ORDER_IDX;如果索引在 TBS_DATA但它的父表在另一个表空间这也是很常见的规划方式没关系。但如果索引和表混在同一个正在膨胀的表空间里建议顺便查一下父表占了多少空间心里好有个数SELECT OWNER, SEGMENT_NAME, SEGMENT_TYPE, ROUND(BYTES / 1024 / 1024 / 1024, 2) AS size_gb FROM dba_segments WHERE TABLESPACE_NAME TBS_DATA ORDER BY BYTES DESC FETCH FIRST 10 ROWS ONLY;这样就可以确认这个表空间里到底谁吃了大头。说不定真正占空间的是几张日志表而那个索引只是被连累的受害者。3. 五种解决方案从应急到彻底解决3.1 方案一直接扩展数据文件resize如果数据文件还没到单文件上限而且服务器磁盘还有空间resize 是最快的处理方式ALTER DATABASE DATAFILE /u01/oradata/TSDB/tbs_data01.dbf RESIZE 32G;注意文件名要跟 dba_data_files 里查出来的完全一致路径写错会报 ORA-01157 找不到文件。另外resize 只能往大了调不能瞎调小调小后如果文件尾部还有数据会报 ORA-03297这个后面单独说。实际操作中我一般分两步走先看当前文件大小再决定能调多大。比如当前是 16G磁盘剩余 200G那就先调到 32G 观察一下不必一步到位免得空间规划失控。3.2 方案二开启或调大自动扩展如果这个库是开发库、测试库平时没人做严格的容量管理直接把自动扩展打开是最省心的ALTER DATABASE DATAFILE /u01/oradata/TSDB/tbs_data01.dbf AUTOEXTEND ON NEXT 512M MAXSIZE 32G;这里 NEXT 参数意思是每次自动增长 512MB不要设得太小。如果设成 1M数据量一上来文件会频繁触发扩容产生不必要的空间分配开销严重时能明显感觉到系统卡顿告警日志里也会刷出一堆Adding additional space to datafile之类的信息。生产环境我建议关掉自动扩展改成手动管理。理由很简单自动扩展容易掩盖空间增长趋势等发现的时候表空间往往已经涨得不可控了而且 maxsize 如果设了 unlimited单个文件可能把磁盘撑爆数据库直接 hang 住比报 ORA-01654 要麻烦得多。3.3 方案三新增数据文件当单个数据文件已经顶到上限比如 8KB 块到 32Gresize 没空间可扩那就在表空间里再添一个数据文件ALTER TABLESPACE TBS_DATA ADD DATAFILE /u01/oradata/TSDB/tbs_data02.dbf SIZE 8G AUTOEXTEND ON NEXT 512M MAXSIZE 32G;新增数据文件比 resize 现有文件更灵活而且能分散 I/O尤其适合大表空间在多磁盘路径上的部署。注意别把所有数据文件放在同一块物理磁盘上不然数据文件数量再多也白搭读写全挤在一条道上。加完文件之后再跑一遍使用率查询确认 TBS_DATA 的 free_gb 涨上来了。然后让业务重试刚才失败的 SQL基本就能恢复正常。3.4 方案四清理无用数据并回收空间加文件只是临时止血如果表空间里全是历史垃圾数据再多的磁盘也扛不住。清理空间要做两件事清数据和让段把空间吐出来。先说清数据如果是日志表、临时中间表可以直接 TRUNCATE。TRUNCATE 是 DDL 操作会清空表数据但保留表结构空间会完全释放给表空间。如果是业务数据按时间条件 DELETE 旧数据这个属于 DML可以加 WHERE 条件控制删多少注意提交频率。关键点来了DELETE 之后段的高水位线不会降。什么意思呢你删掉了表里 80% 的行但这个表段占用的数据块并没有全部释放下次再插入数据时Oracle 会优先重用那些空了的块。从段的视角看空间确实富余了但从表空间的视角看这个段仍然圈着大把空间空闲列表里可能仍然显示空间紧张。想让 DELETE 之后的空间真正回到表空间层面可以执行段收缩ALTER TABLE USR_ORDER LOGGING ENABLE ROW MOVEMENT; ALTER TABLE USR_ORDER SHRINK SPACE CASCADE;SHRINK 需要开启行迁移而且对正在被高频访问的大表会产生额外的 I/O 和锁竞争建议在维护窗口执行。如果表太大SHRINK 做不动也可以考虑 MOVE 到新表空间再还回来代价是相关的索引要重建。索引本身如果碎片化严重也可以重建ALTER INDEX USR_ORDER_IDX REBUILD;REBUILD 之后索引段会重新紧凑排列不仅缩小空间占用还能提升查询效率。但要注意重建索引期间 DML 会被阻塞务必安排在低峰期。3.5 方案五段迁移与表空间重组如果你的系统里存在多个表空间而当前爆满的 TBS_DATA 里正好有对象其实没必要待在这那就把它们挪出去给紧急的表腾地方。比如把一张历史表移到 TBS_HISTORYALTER TABLE USR_ORDER_LOG MOVE TABLESPACE TBS_HISTORY; ALTER INDEX USR_ORDER_LOG_IDX REBUILD TABLESPACE TBS_HISTORY;这里要特别注意表 MOVE 到新表空间之后索引不会跟着动所以复制的索引必须 REBUILD否则会变成 UNUSABLE 状态查询直接报 ORA-01502。这个坑我踩过不止一次每次都要提醒自己 MOVE 表和 REBUILD 索引必须成对出现。3.6 五种方案的选择逻辑把这五个方案摆一起看选择顺序其实很清楚场景首选方案备注数据文件未到上限磁盘充足resize 或开启 autoextend应急最快单数据文件到上限新增数据文件同时看是否有清理空间必要表空间里垃圾数据居多清理数据 SHRINK治本但注意窗口索引本身碎片严重REBUILD 索引顺带提升性能表放错位置表空间规划不合理MOVE 表 REBUILD 索引中长期方案我的习惯是先解决眼前报错保证业务恢复然后立刻把容量趋势查出来决定是清理还是扩容最后把监控和告警补上。4. 防患于未然表空间监控与容量规划4.1 一套可以直接用的监控 SQLORA-01654 这种报错最理想的情况是在它发生之前就通过监控发现。下面这个 SQL 我基本见库就执行一遍把所有表空间的使用率一次列出来SELECT d.tablespace_name, ROUND(SUM(d.bytes) / 1024 / 1024 / 1024, 2) AS total_gb, ROUND(SUM(d.bytes - NVL(f.bytes, 0)) / 1024 / 1024 / 1024, 2) AS used_gb, ROUND(NVL(SUM(f.bytes), 0) / 1024 / 1024 / 1024, 2) AS free_gb, ROUND((1 - NVL(SUM(f.bytes), 0) / SUM(d.bytes)) * 100, 2) AS used_pct FROM dba_data_files d LEFT JOIN (SELECT tablespace_name, SUM(bytes) bytes FROM dba_free_space GROUP BY tablespace_name) f ON d.tablespace_name f.tablespace_name GROUP BY d.tablespace_name ORDER BY used_pct DESC;再配合一条查剩余空间碎片的 SQLSELECT TABLESPACE_NAME, COUNT(*) AS free_extents, ROUND(MAX(BYTES) / 1024 / 1024, 2) AS max_fragment_mb, ROUND(SUM(BYTES) / 1024 / 1024, 2) AS total_free_mb FROM dba_free_space GROUP BY TABLESPACE_NAME ORDER BY total_free_mb DESC;如果 max_fragment_mb 远小于 total_free_mb说明这个表空间碎片化严重总有一天会出现明明有空间但就是分配不出来的情况。这种情况光看使用率是发现不了的必须靠这条 SQL。4.2 巡检节奏和告警阈值空间巡检的节奏我建议至少一周一次数据增长快的库每天一次。可以用 shell 的 crontab 把上面的 SQL 包装成一个脚本超过阈值就往钉钉或邮件发消息逻辑非常简单效果比人肉巡检可靠得多。阈值设置也不要一刀切。我说个经验值仅供参考使用率超过 85% 时列入关注开始评估未来两周的增长量。超过 92% 时准备扩容方案能扩的赶紧扩。超过 97% 时立即处理已经进入危险区。这个阈值对核心生产库可以更保守比如 80% 就开始动手规划。4.3 容量规划建议临时抱佛脚只能是应急表空间规划应该在建表时就做好。几个建议业务表与索引分开表空间这是最常见的做法索引表空间单独规划可以避免索引和表争抢空间。按分区表管理大表把历史数据按月份分区旧分区可以单独移动到慢速存储表空间甚至定期 DROP 老分区。UNDO 和 TEMP 单独预留UNDO 表空间太小会导致 ORA-01555 和事务失败TEMP 表空间太小会导致排序 SQL 报 ORA-01652这俩都是独立的坑不要和数据表空间混在一起算容量。定期评估增长趋势查询 dba_segments 按月统计每个表空间的增长量对后续扩容很有参考价值。5. 常见问题速查与避坑实录5.1 resize 时碰到 ORA-03297 怎么办ORA-03297 的意思是在你指定缩小到的目标位置之后文件里还有数据占着块所以不能收缩。比如你想把 32G 文件缩小到 20G但文件在 20G 之后的地方仍有段在使用。处理办法分两步定位这个文件里靠后的段SELECT OWNER, SEGMENT_NAME, SEGMENT_TYPE, BLOCK_ID, BLOCKS FROM dba_extents WHERE FILE_ID file_id ORDER BY BLOCK_ID DESC;把这些段 MOVE 到其他表空间或先导出再导入腾出尾部的块之后再重新 resize。不得不说这个过程比较折腾。所以在生产环境我通常只往大调基本不做缩小操作。真到需要缩小空间的地步说明表空间规划已经出大问题了不如直接重建一个合理大小的新表空间把数据迁过去。5.2 清完数据还是报 ORA-01654如果你执行了 DELETE表空间使用率也确实降下来了但第二天又报 ORA-01654那大概率是高水位线的问题。我在 3.4 节已经提过这里再强调一遍DELETE 释放的空间还留在段的肚子里没有还给表空间。只有 TRUNCATE 是直接把高水位线拉下来并释放全部空间DELETE 则需要配合 SHRINK SPACE 才能让空间真正回归表空间。另外还有一种可能表空间里剩余的是零碎空间。比如 free_gb 显示有 6G但最大连续空闲区只有 100MB而你那个索引需要的区间大小恰好大于 100MB。这种情况下加一个新的数据文件反而能立刻解决问题因为新文件是连续的。5.3 UNDO 表空间相关的 ORA-01654重看报错信息如果出问题的是 UNDO 表空间比如ORA-01654: unable to extend index _SYSSMU1_4242123456$ by 64 in tablespace UNDOTBS1这里扩展失败的是 UNDO 段。原因是 UNDO 表空间不足事务需要回滚信息但没地方写了。处理办法是给 UNDO 表空间增加数据文件ALTER TABLESPACE UNDOTBS1 ADD DATAFILE /u01/oradata/TSDB/undotbs02.dbf SIZE 8G AUTOEXTEND ON NEXT 512M MAXSIZE 16G;同时检查是不是有长时间未提交的大事务或者 UNDO_RETENTION 配得过大导致 UNDO 被强制保留。如果业务上经常跑大批量 UPDATE 又没提交UNDO 空间会像漏水的桶一样快速见底。5.4 临时表空间满了怎么办如果报错信息里出现 temp segment 或者 ORA-01652那是临时表空间的问题而不是数据表空间。排序、哈希连接、分组聚合这些操作都会临时占用 TEMP 表空间。处理方法ALTER TABLESPACE TEMP ADD TEMPFILE /u01/oradata/TSDB/temp02.dbf SIZE 4G AUTOEXTEND ON NEXT 512M MAXSIZE 16G;如果是多个实例共用一个 TEMP 表空间配置会有点讲究但核心思路和 ORA-01654 一致空间不够就给它空间。临时表空间的空间不足有时候重启实例也能清理掉一部分挂着的临时段但这不是治本的办法该扩还得扩。5.5 索引所在表空间的碎片陷阱前面提到过总空闲够但连续空闲不够的情况。在实际故障中这类问题最让人迷惑因为你跑使用率查询发现 free_gb 明明有 5G但业务就是报错。Oracle 分配区间时如果表空间是本地管理且统一区大小uniform size模式那么每个区间的大小是一样的。如果你建表空间时设了 UNIFORM SIZE 8M而表空间里只剩下大量 1M、2M 的零星碎片那 8M 的统一区间根本分配不出来。这种情况的解法要么加数据文件要么用 AUTOALLOCATE 模式重建表空间。所以创建表空间的时候除了关注初始大小一定要想清楚区间的分配方式。碰到 ORA-01654 且 free 空间不少时也要主动去查一下 dba_free_space 的碎片分布别被表面的使用率糊弄过去。5.6 报错速查表最后给一份速查表方便以后直接对着排查检查项命令或视图判断标准表空间使用率dba_data_files dba_free_space使用率超过 97% 必须处理数据文件是否到上限dba_data_files 的 MAXBYTESSIZE_MB 等于 MAX_MB 即到顶是否开启自动扩展dba_data_files 的 AUTOEXTENSIBLENO 会导致空间锁死空闲碎片分布dba_free_spaceMAX(BYTES) 是否满足区间需求段大小排名dba_segments确定空间消耗大头索引状态dba_indexes 的 STATUSUNUSABLE 需要重建告警日志alert_log查所有 ORA- 错误的时间线6. 最后说几句大实话ORA-01654 这个报错本身不难解决难的是每次都在你最不想出问题的时候突然冒出来。我自己的体会是绝大多数 ORA-01654 不是突然发生的而是容量管理长期缺位后的必然结果。只要一开始就做好监控、规划好表空间、控制好自动扩展这个报错基本可以完全避免。最后分享一个小习惯每次处理完这类报错我都会把 alert 日志里当天的错误时间点、处理动作、最终效果记一笔。攒上几个月回看你会发现哪些表空间是常客哪些业务表是吞空间的黑洞后续做扩容和优化就有据可依了比每次当救火队员舒服得多。