
差不多每个Oracle DBA都被这样的告警吵醒过凌晨一两点某个核心业务表空间使用率突破95%应用开始报 ORA-01653: unable to extend table。你打开监控一看数据文件几乎撑满但业务还在涨这时候到底是加数据文件、扩文件、还是收缩某个大段每一步都像在走钢丝。这篇是Oracle 19c入门学习教程系列中专门拆解表空间与数据文件管理的部分我会把存储架构、创建选型、日常扩容、文件移动、误删恢复以及TEMP和UNDO这两个特殊表空间的坑一次讲透适合刚从11g/12c转到19c的同行也适合被表空间问题折磨过几次的初级DBA直接照抄。1. 表空间和数据文件先搞懂这对逻辑与物理的关系1.1 从数据库到数据块存储的五个层次很多人建表空间时只关心一句话“建一个10G的文件丢进去”这没有错但等你要解释“为什么表空间明明有空间表却扩展不了”的时候就绕不开Oracle的层次化存储模型。完整的链条是这样的数据库Database下面挂着若干个表空间Tablespace表空间是逻辑容器表空间里面有段Segment比如表段、索引段、Undo段、临时段段由区Extent组成区是一组连续的数据块数据块Data Block是I/O的最小单位默认8KB最后所有这些物理内容最终落在数据文件Data File上。这个结构看起来绕但拆分逻辑和物理两个层面恰恰是Oracle最聪明的设计。逻辑层管的是“对象怎么组织空间”物理层管的是“文件怎么放、放哪里”。这样带来的直接好处是你做表空间级管理时不需要关心底层文件具体在哪个目录而做文件迁移时又不需要动对象定义。理解这一点后面所有操作都有了“为什么”的依据。实操中你只靠两个视图就能把这条链看清楚。V$TABLESPACE给出表空间的编号和名称DBA_DATA_FILES则把表空间ID和数据文件的路径、大小、是否自动扩展一一对应。一条SQL就是你日常巡检的起点SELECT d.tablespace_name, d.file_name, ROUND(d.bytes / 1024 / 1024, 2) AS size_mb, ROUND(d.bytes / 1024 / 1024, 2) AS max_mb FROM dba_data_files d ORDER BY d.tablespace_name, d.file_name;1.2 SYSTEM与SYSAUX官方表空间的管理红线Oracle安装完成后会自动创建一组系统表空间很多人以为这些和普通表空间一样可以随便建表、随便删对象。这里我直接给结论SYSTEM和SYSAUX是两块雷区踩一次就够你折腾半天的。SYSTEM表空间存的是数据字典、PL/SQL包、视图定义、审计信息等。它不能offline不能改名更不能drop。你手动把业务表建在SYSTEM上短期内不会报错但一旦数据字典增长过快、SYSTEM文件膨胀到磁盘满整个库都会变得极度脆弱恢复起来非常痛苦。SYSAUX是System Auxiliary的缩写承载了AWR快照、统计信息、EM仓库、SQL优化集等一大堆辅助功能。从11g开始Oracle就把原来SYSTEM里非核心的内容挪到了SYSAUX让SYSTEM更干净。但注意SYSAUX也有自己的容量红线如果AWR retention设置过长或者某个统计任务异常SYSAUX也会被塞满典型症状是AWR报告生成失败。我的建议很朴素任何业务对象都不要往SYSTEM/SYSAUX放哪怕它很小。建表空间时按业务模块命名比如APP_DATA、APP_IDX把业务数据、索引、临时排序分别隔开。这样不仅方便定位空间问题出故障时也能缩小排查范围。多数生产事故里SYSTEM/SYSAUX异常增长背后都是“有人图省事”埋下的雷。2. 创建表空间的选型决策影响几年后的是这些参数2.1 OMF还是手工路径创建表空间时第一个纠结往往是文件路径怎么写。Oracle提供了OMFOracle Managed Files机制只要设置了DB_CREATE_FILE_DEST参数你创建表空间时可以完全不写文件名Oracle会自动在目标目录下生成一套带唯一名字的数据文件删除表空间时对应的文件也会自动清理。这对开发库或测试库非常友好省去手工维护文件清单。但生产环境我强烈建议显式指定路径理由很实际。第一OMF生成的文件名是类似ORA12C_DATA_FILE_12345.dbf这种看着就头大出问题时排查文件归属很费劲第二生产库往往要考虑I/O分布把不同业务的表空间放在不同挂载点OMF的“自动”反而会打乱你的规划第三很多企业有标准化目录规范审计时要能一眼从路径看出是哪个业务。显式指定时路径规划就是一门学问。不要把同一组磁盘的所有文件堆在一个目录里否则系统I/O会全部卡在一条通道上。做过存储的人应该有体会把所有数据文件放在一个磁盘组里高峰期会出现某个文件读写等待极高而另一块盘闲着。我处理过一套系统就是用户把所有表空间都建在单一挂载点后来不得不通过数据文件移动把负载拆到两个存储资源池才把AWR里的那些enq: TX - row lock contention和db file sequential read压下来。创建语句是基本功但很多人写得不规范CREATE TABLESPACE app_data DATAFILE /u02/oradata/ORCL/app_data01.dbf SIZE 10G AUTOEXTEND ON NEXT 1G MAXSIZE 32G EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT AUTO;2.2 extent管理与段空间管理怎么搭配这两个参数很多文档一句话带过但它们在后面的空间碎片化、段收缩、性能调优阶段会反复出现。EXTENT MANAGEMENT LOCAL是Oracle 9i之后唯一合理的选择。它把区的分配信息记录在每个数据文件的位图里避免了字典管理表空间的递归操作。在这个基础上还有两种区分配策略AUTOALLOCATE和UNIFORM SIZE。AUTOALLOCATE让Oracle根据对象增长自动决定区大小前期开发省心缺点是区大小不固定大量小表反复删除插入之后容易产生碎片。UNIFORM SIZE 1M则强制每个区固定大小空间管理可预期适合大小稳定的业务表。我的经验是OLTP业务表空间用AUTOALLOCATE问题不大如果你是要存放大量分区表、或者预先知道段会以固定步长增长用UNIFORM SIZE更合适。SEGMENT SPACE MANAGEMENT决定段内部的空闲空间怎么管理。AUTO是默认且推荐的方式它会用位图跟踪块的使用状态并发插入时能减少热块争用而MANUAL方式是传统的freelist链表管理并发高的情况下很容易出现buffer busy wait。除非你在兼容老版本特性的系统里否则一律用AUTO没有犹豫空间。顺带提醒一个容易被忽略的点非标准块大小。默认块大小由DB_BLOCK_SIZE决定通常是8K除非你明确知道为什么需要2K、4K、16K甚至32K块否则不要为了“某个大表IO可能更爽”去建非标准块大小的表空间。非标准块会额外占用buffer cache中的不同尺寸缓存池管控不好反而增加管理成本。2.3 BIGFILE与SMALLFILE怎么选创建表空间的默认类型是SMALLFILE也就是小文件表空间一个表空间可以放最多1022个数据文件。这种模式的好处是灵活单个文件不够就加一个。坏处是文件多了以后控制文件管理、备份恢复和巡检脚本都会变繁琐。BIGFILE表空间则刚好相反整个表空间只允许一个数据文件但这个数据文件理论上大得惊人可以到128TB甚至更高取决于块大小。19c在ASM环境或超大型数据仓库里很推崇BIGFILE因为你在ASM磁盘组上只需要维护一条路径RMAN备份也不用管一堆小文件。但选择BIGFILE有个很实际的坑它把“表空间容量规划”的压力集中到了一个文件上。一旦这个文件达到上限你没有任何“再加一个文件”的余地只能重新规划或迁移。所以我在实践中通常建议默认保持SMALLFILE除非你能明确说出“我需要BIGFILE是因为……”的理由。比如表空间和磁盘组一对一映射或者你管理的是几千个表空间的云环境靠BIGFILE能显著减少数据文件数量。另外查询表空间类型有个简单SQLSELECT tablespace_name, bigfile FROM dba_tablespaces;看到BIGFILEYES就要清楚这个表空间只有一个数据文件容量风险被集中在单个文件上。3. 扩容、移动与误删恢复数据文件日常操作的手术台3.1 扩容的三种姿势与边界表空间空间告警后DBA通常有三个动作RESIZE扩充现有文件、开启AUTOEXTEND、或者新增数据文件。三者看起来都是“加空间”但边界完全不同。ALTER DATABASE DATAFILE ... RESIZE是直接改文件大小可以扩大也可以缩小。但它有个硬限制一个数据文件不能缩小到低于其内部已使用块的高水位线High Water Mark。所以你会发现明明表空间空了30%RESIZE却报 ORA-03297: file contains used data beyond requested RESIZE value。这时候不能硬刚得先把段收缩或移动再RESIZE后面我会专门讲。AUTOEXTEND ON是最容易被滥用的功能。我见过很多生产库把数据文件设成AUTOEXTEND ON NEXT 100M MAXSIZE UNLIMITED这相当于给磁盘空间埋了一颗定时炸弹。文件无限涨最后磁盘满整个库挂掉连归档日志都写不进去。正确做法是给MAXSIZE设置一个明确的上限比如文件最大32G或64G同时配合监控在阈值之前介入。而且要注意AUTOEXTEND只在当前文件写满时才触发扩展扩展期间会短暂持有空间管理相关的锁并发高的系统里可能带来轻微的性能毛刺。ALTER TABLESPACE ... ADD DATAFILE则是增加一个新数据文件。它适合SMALLFILE表空间是生产环境最安全、最常用的扩容方式因为不会触碰现有文件的结构。操作时建议同时指定SIZE和AUTOEXTEND新文件上线后才有足够的初始分配不至于一上来就触发扩展。经常有人问“表空间在线扩容会影响业务吗”这句话要分情况。ADD DATAFILE和RESIZE不会导致表空间offline通常可以在线执行但要注意磁盘I/O和空间锁的瞬时影响。生产环境扩容前我会先做一次空间使用率快照再选择低峰期操作。3.2 移动数据文件的标准流程与翻车点移动数据文件的理由很多磁盘迁移、目录重新规划、文件从本地盘往ASM搬。步骤看起来简单但几乎每周都有人在网上问“为什么我改了路径库起不来了”。标准流程是四条腿走路offline表空间、物理移动文件、rename路径、online表空间。以把app_data01.dbf从/u01挪到/u02为例ALTER TABLESPACE app_data OFFLINE NORMAL;确认表空间确实offline后在操作系统层面移动文件mv /u01/oradata/ORCL/app_data01.dbf /u02/oradata/ORCL/app_data01.dbf然后通知Oracle路径变更ALTER TABLESPACE app_data RENAME DATAFILE /u01/oradata/ORCL/app_data01.dbf TO /u02/oradata/ORCL/app_data01.dbf;最后恢复在线ALTER TABLESPACE app_data ONLINE;翻车点通常有三个。第一个是步骤顺序有人先rename再mv文件系统上找不到文件数据库直接报 ORA-01157 cannot identify/lock data file。第二个是offline期间业务写入报错如果你对业务连续性要求高应该提前评估offline窗口或者使用在线重定义、或者先把表移到别的表空间再操作。第三个是移动时没有确认目标目录所属文件系统有足够空间、权限正确、目标路径不存在同名文件一个mv命令搞错就变成“文件神秘失踪”。还有一个更隐蔽的坑如果你用的是OMF模式RENAME时不能直接指定任意路径Oracle会按OMF规则重新生成文件名。这时候最好先查清OMF下的文件路径再按规则移动不要凭猜测写路径。如果你移动的是SYSTEM表空间或者数据库不在open状态时你想改文件路径流程不一样。那需要在mount状态下使用ALTER DATABASE RENAME FILE ... TO ...这属于启动恢复场景。普通表空间的移动用表空间级RENAME就够了别拿DATABASE级命令去折腾。3.3 数据文件被误删别慌按顺序来数据文件被删是每个DBA的噩梦但我要说的是误删之后最忌讳的是乱操作冷静评估、按顺序恢复大多数情况下能救回来。先讲一个前提如果数据库还在运行只是数据文件在操作系统层面被删了这个时候不要立刻shutdown数据库。因为进程可能仍然持有文件句柄一旦shutdown你再想通过句柄找文件就难了。这种情况可以尝试从/proc/数据库进程PID/fd目录下复制仍被打开的文件句柄到原路径能救回完整文件。一些老DBA用这招救过很多次运维事故。如果文件真实损坏或者无法恢复而你处于归档模式且有RMAN备份标准流程是ALTER DATABASE DATAFILE /u02/oradata/ORCL/app_data01.dbf OFFLINE; RESTORE DATAFILE /u02/oradata/ORCL/app_data01.dbf; RECOVER DATAFILE /u02/oradata/ORCL/app_data01.dbf; ALTER DATABASE DATAFILE /u02/oradata/ORCL/app_data01.dbf ONLINE;这里有个容易出错的概念RECOVER DATAFILE会把归档日志和联机日志里的变更全部apply到这个数据文件上。如果当前日志也丢了或者文件损坏太严重可能只能接受部分数据丢失。那什么时候用OFFLINE DROP我只在一种情况下考虑非系统表空间、文件损坏且无法恢复、业务允许丢失该表空间的数据。注意这是“放弃”操作不是“恢复”操作。ALTER DATABASE DATAFILE ... OFFLINE DROP之后这个数据文件被标记为需要删除如果你之后还想用它还得通过RMAN或重建表空间来恢复。生产环境务必慎重审计时这个命令也会被重点盯防。我个人的建议与其练就一身“救火”本领不如在平时把数据文件清单、RMAN备份策略、控制文件自动备份都做好。尤其在19c环境里一次CONFIGURE CONTROLFILE AUTOBACKUP ON加上定期备份能让你在误删文件后的半小时内稳坐下来恢复而不是满脑子“完了”。4. 临时表空间与UNDO表空间被忽视的两个特殊角色4.1 临时表空间组与排序风暴永久表空间大家盯得紧临时表空间却经常被遗忘直到某天一条大SQL把临时表空间打爆数据库抛 ORA-01652: unable to extend temp segment by 128 in tablespace TEMP全公司都看到这条报错。临时表空间承载的是排序、hash join、临时表这类操作。排序操作如果超过PGA的SORT_AREA_SIZE19c里由PGA_AGGREGATE_TARGET统一管理就会溢出到临时表空间。多个大排序并发时临时表空间瞬间吃满并不是新闻。创建临时表空间的语法和永久表空间稍有不同用TEMPFILE而不是DATAFILECREATE TEMPORARY TABLESPACE app_temp TEMPFILE /u02/oradata/ORCL/app_temp01.dbf SIZE 10G AUTOEXTEND ON NEXT 1G MAXSIZE 32G EXTENT MANAGEMENT LOCAL UNIFORM SIZE 16M;临时表空间的高级玩法是表空间组Tablespace Group。你可以创建多个临时表空间再让它们组成一个组数据库用户在这个组里做排序时Oracle会按负载自动分摊到组内不同的临时表空间上ALTER TABLESPACE app_temp1 TABLESPACE GROUP tmp_group; ALTER TABLESPACE app_temp2 TABLESPACE GROUP tmp_group;这样做的好处是避免单个临时文件排序全部挤在一起同时对“临时表空间被占满”有天然缓冲。在19c上临时表空间还支持在线收缩这非常实用ALTER TABLESPACE app_temp SHRINK SPACE KEEP 8G;这条命令能将临时表空间收缩到保留8G前提是没有正在使用的临时段。实测下来在清理完一次大型报告查询后临时表空间从30G缩到10G只用了秒级时间比重建临时表空间省事得多。4.2 UNDO表空间与ORA-01555的根源如果说临时表空间是“排序的缓冲区”那UNDO表空间就是“时光倒流的仓库”。事务修改数据前Oracle把旧值写入UNDO段这样其他会话在一致性读时可以读到旧版本事务回滚时也有数据可依闪回查询更是直接靠UNDO里的旧镜像。UNDO空间管理的关键参数是UNDO_RETENTION秒。它决定了事务提交后UNDO里的旧版本最少保留多久。很多人以为设大就安全其实不完全是。UNDO_RETENTION只是“目标保留时间”如果UNDO表空间满了Oracle还是会重用过期的UNDO区。只有当表空间设置了RETENTION GUARANTEE时Oracle才绝对不会重用还没到达保留期的UNDO区ALTER TABLESPACE undo1 RETENTION GUARANTEE;但这个操作需要谨慎一旦设置UNDO表空间满时事务可能直接报错不会自动复用。ORA-01555: snapshot too old 是Oracle世界里最经典的错误之一。它本质上是某个查询需要读取一个旧版本的数据块但UNDO表空间里对应的旧版本已经被新事务覆盖了。常见场景是一个长时间运行的查询在它执行期间有大量事务反复提交UNDO区被飞速消耗旧镜像被覆盖查询再回头读时发现“回不去了”。排查ORA-01555第一步看V$UNDOSTAT里的MAXQUERYLEN和TUNED_UNDORETENTION。如果MAXQUERYLEN很大说明这个系统确实有长查询需求第二步看UNDO表空间是否频繁处于满状态。19c的自动调优机制会根据查询长度动态调整UNDO保留时间但如果UNDO表空间太小自动调优也无济于事只能扩容或者优化业务SQL把那些运行几个小时的“全表扫描式”查询拆掉。另外监控UNDO表空间不要只看使用率百分比还要看ACTIVE区占用量。ACTIVE区是当前尚未提交事务用到UNDO这部分不能覆盖一旦ACTIVE区长时间占据大量空间很可能是有长事务卡住了SELECT BEGIN_TIME, MAXQUERYLEN, TUNED_UNDORETENTION FROM v$undostat ORDER BY begin_time DESC FETCH FIRST 5 ROWS ONLY;5. 空间告警之后监控SQL与应急处置节奏5.1 能直接上手的监控SQL表空间使用率监控是每个DBA最基础的日常但很多人的监控SQL写得不够严谨。永久表空间和临时表空间要分开处理UNDO又要单独判断。永久表空间的使用率我推荐用这个经典查询SELECT a.tablespace_name, ROUND(a.bytes / 1024 / 1024, 2) AS total_mb, ROUND(b.bytes / 1024 / 1024, 2) AS used_mb, ROUND((a.bytes - b.bytes) / 1024 / 1024, 2) AS free_mb, ROUND((b.bytes / a.bytes) * 100, 2) AS used_pct FROM (SELECT tablespace_name, SUM(bytes) bytes FROM dba_data_files GROUP BY tablespace_name) a LEFT JOIN (SELECT tablespace_name, SUM(bytes) bytes FROM dba_free_space GROUP BY tablespace_name) b ON a.tablespace_name b.tablespace_name ORDER BY used_pct DESC;临时表空间不能简单用DBA_FREE_SPACE看它会显示0。要用系统视图SELECT tablespace_name, ROUND(tablespace_size / 1024 / 1024, 2) AS total_mb, ROUND(free_space / 1024 / 1024, 2) AS free_mb, ROUND((tablespace_size - free_space) / 1024 / 1024, 2) AS used_mb FROM dba_temp_free_space;UNDO表空间的使用率也不能只看剩余空间重点是ACTIVE区有多少。通过DBA_UNDO_EXTENTS按状态统计SELECT status, COUNT(*) AS extents_cnt, ROUND(SUM(bytes)/1024/1024,2) AS mb FROM dba_undo_extents GROUP BY status;当STATUSACTIVE的区长期居高不下就该检查是不是有长事务或者哪个会话一直握着UNDO不释放。告警阈值的设置也讲究。永久表空间我习惯设85%预警、92%严重临时表空间设置预警时会考虑峰值如果峰值并发排序多阈值可以放宽到80%但要在告警之后能快速扩容UNDO表空间重点盯ACTIVE区一旦ACTIVE超过UNDO总量50%这个信号比单纯的使用率百分比更有意义。5.2 空间不足时的处置优先级表空间告警触发后很多新手会直接加数据文件扩容。这不是错但不一定是最优解。我建议的处置顺序是先定位空间被谁吃了再决定是清理、收缩、还是扩容。第一步找出表空间里占用空间最大的段SELECT segment_name, segment_type, owner, ROUND(bytes / 1024 / 1024, 2) AS size_mb FROM dba_segments WHERE tablespace_name APP_DATA ORDER BY bytes DESC FETCH FIRST 20 ROWS ONLY;如果发现是临时段那可能是大排序操作引发等会话结束空间会自动释放如果是索引段异常巨大可能是索引碎片堆积考虑重建如果是表段就看是不是历史分区表堆积了太多过期分区。可清理的空间里优先级最高的是临时表空间上挂着的临时段、过期归档日志、和那些“留了三年没查过一次”的历史分区。清理之后空间如果还不够才轮到段级收缩。19c里可以ALTER TABLE app_data ENABLE ROW MOVEMENT; ALTER TABLE app_data SHRINK SPACE CASCADE;注意SHRINK SPACE不是没有代价的。它会移动行产生大量UNDO和REDO而且会触发行迁移在线业务高峰期跑这个操作容易引发ORA-08102: index not found或者锁等待。我一般把它放到维护窗口执行。如果所有收缩手段都用完了最后还是不够那就是业务真的在增长该扩容就扩容不要因为“觉得不该扩容”而克扣空间。5.3 数据文件想缩却缩不动高水位线问题不只是扩容有讲究缩容同样有坑。很多DBA为了压缩空间把数据文件RESIZE想变小却遇到 ORA-03297原因就是段的高水位线高于目标文件大小。高水位线是段内曾经达到的最高使用位置。即使你删了表里90%的行高水位线不会自动降下来表的全表扫描还是扫描高水位线以下的块。所以数据文件缩不缩取决于段的高水位线而不是表空间的剩余量。想真正缩小文件大小必须先把段的高水位线降下来。常规手段是先做段收缩再RESIZEALTER TABLE app_data SHRINK SPACE CASCADE; ALTER DATABASE DATAFILE /u02/oradata/ORCL/app_data01.dbf RESIZE 20G;如果表特别大SHRINK SPACE会跑很久也可以考虑在线表重定义DBMS_REDEFINITION或者用ALTER TABLE ... MOVE加UPDATE INDEXES把段重建一遍然后再RESIZE。还有更简单粗暴但有效的方式用DataPump把这部分数据导出来重建表空间再导回去高水位线问题直接消失但停机时间要评估。我在生产环境中的体会是不要频繁RESIZE数据文件。反复扩大再缩小会留下大量碎片最终导致文件内部空间利用率不均下次使用率计算时数字好看但实际分配不出来。空间规划如果能预留20%的缓冲绝大多数表空间告警都不会演变成为事故。5.4 19c带来的几个管理变化既然我们用的是Oracle 19c这里必须提几个和表空间管理直接相关的新变化免得你们还在用老经验处理新问题。首先是表空间级别的默认压缩。19c创建表空间时可以指定默认行压缩以后这个表空间里新建的表都自动继承压缩属性对存储省量非常友好CREATE TABLESPACE app_data DATAFILE /u02/oradata/ORCL/app_data01.dbf SIZE 10G AUTOEXTEND ON NEXT 1G MAXSIZE 32G DEFAULT ROW STORE COMPRESS ADVANCED;其次是自动块损坏检测。19c在读取块时会自动检测物理块损坏并将损坏块记录到V$DATABASE_BLOCK_CORRUPTION。这配合RMAN的BLOCKRECOVER命令可以实现块级恢复不用把整个数据文件甚至整个表空间都捞出来恢复。以前遇到坏块DBA的第一反应是找备份恢复整个文件19c下可以先查视图确认坏块范围再做块级恢复恢复时间从小时级降到分钟级。另外19c的临时表空间收缩命令已经比较成熟如前文所说ALTER TABLESPACE ... SHRINK SPACE可以直接使用。这一点对经常跑大查询的系统非常实用空间回收不再需要重启实例或重建临时表空间。最后多说一句版本问题。Oracle 19c是长期支持版本补丁更新会持续很久。目前看到19c的版本号已经打到19.25.0.0.241015这种补丁级别不同的补丁级别里某些空间管理的默认行为可能略有差异。生产环境升级补丁后最好把表空间管理相关初始化参数重新确认一遍比如DB_CREATE_FILE_DEST、UNDO_RETENTION、TEMP_TABLESPACE这些别让旧配置在19c新内核下“带病运行”。回到开头说的那个被OOM告警吵醒的夜晚。我现在的习惯是把数据文件清单、表空间监控SQL、RMAN备份策略都提前整理好告警来了先看段排行再决定是清理还是扩容最后做一次完整的空间记录。维护这套“台账”并不难难的是在没事的时候坚持做。别等到数据文件被误删、表空间100%撑死的那一刻才想起来原来平时半小时就能做完的巡检救的是整个业务系统的命。