☰
Oracle 表空间监控与智能扩容实战:从“假使用率“到“存储感知扩容“
2026/10/8 13:09:10 网站建设 项目流程

一篇写给一线 DBA 的实操笔记。不堆概念,只讲清楚三件事:使用率到底该怎么算、扩容该往哪里扩、ASM 满了该怎么办。


一、先破除一个最常见的错觉

几乎每个 Oracle 监控脚本的第一版,都是这么写的:

SELECTa.tablespace_name,ROUND(a.total_size-NVL(b.free_size,0),2)ASused_mb,ROUND((a.total_size-NVL(b.free_size,0))/a.total_size*100,2)ASpctFROM(SELECTtablespace_name,SUM(bytes)/1024/1024total_sizeFROMdba_data_filesGROUPBYtablespace_name)a,(SELECTtablespace_name,SUM(bytes)/1024/1024free_sizeFROMdba_free_spaceGROUPBYtablespace_name)bWHEREa.tablespace_name=b.tablespace_name(+);

它算的是**“已分配空间用了多少”,而不是"这个表空间还能长多大"**。

举个具体的例子。USERS表空间只有一个数据文件:初始 100MB、AUTOEXTEND ON、MAXBYTES 32GB,当前用了 90MB。

  • 按上面的 SQL:90/100 = 90%,值班电话立刻响。
  • 实际情况:这个文件还能自动长到 32GB,90/32768 ≈ 0.27%。

一个报警,一个其实很闲。误报多了,人就会开始无视告警——这比漏报更危险。

所以监控的第一步,不是加阈值,而是把"分母"换对。


二、正确的容量模型:上限不是一个数,而是三个数的较小值

很多人以为数据文件的上限就是MAXBYTES。其实它同时被三重约束卡住:

约束层说明
① 数据文件自身DBA_DATA_FILES.MAXBYTES(AUTOEXTEND ON时生效;NO时上限就是当前BYTES)
② 引擎结构上限非 bigfile(smallfile)数据文件最多2^22 - 1个块;8K 块约 32GB,32K 块约 128GB
③ 底层存储文件系统或 ASM 磁盘组的可用空间——磁盘组只剩 5GB,文件就长不到 32GB

真正可扩展容量 = min(①, ②, ③)。

这也解释了为什么"表空间使用率"这个概念,在多文件、跨存储时特别容易算错:表空间的可扩展容量是它所有数据文件可扩展容量之和;如果文件分散在不同文件系统或不同 ASM 磁盘组上,必须分别按各自的存储上限算,不能简单加总。

一个够用的真实使用率查询:

SELECTdf.tablespace_name,ROUND(SUM(df.bytes)/1024/1024,2)ASallocated_mb,ROUND((SUM(df.bytes)-NVL(fs.free_bytes,0))/1024/1024,2)ASused_mb,ROUND(SUM(CASEWHENdf.autoextensible='YES'THENdf.maxbytesELSEdf.bytesEND)/1024/1024,2)ASmax_extendable_mb,ROUND((SUM(df.bytes)-NVL(fs.free_bytes,0))/SUM(CASEWHENdf.autoextensible='YES'THENdf.maxbytesELSEdf.bytesEND)*100,2)ASreal_pct_usedFROMdba_data_files df,(SELECTtablespace_name,SUM(bytes)free_bytesFROMdba_free_spaceGROUPBYtablespace_name)fsWHEREdf.tablespace_name=fs.tablespace_name(+)GROUPBYdf.tablespace_name,fs.free_bytesORDERBYreal_pct_usedDESC;

MAXBYTES的默认值陷阱:建库时若只写AUTOEXTEND ON而不写MAXSIZE,默认上限是块大小 × 2^22 - 1(8K 块约 32GB)。所以看到MAXBYTES是一个奇怪的接近 32GB 的值,别惊讶,那是默认值。

关于DBA_TABLESPACE_USAGE_METRICS:方便,但别全信

Oracle 自带的DBA_TABLESPACE_USAGE_METRICS用起来很省事,但它本质是MMON 进程产出的缓存指标,有延迟、非实时。更麻烦的是它有几个已知坑:

  • 数据文件数达到db_files上限时,底层GV$FILESPACE_USAGE返回空,该视图跟着返回空——监控脚本会"看不到任何表空间",非常隐蔽(对应 Doc ID 1903251.1)。
  • 12c 早期版本对 UNDO 表空间的使用率报告不准确:视图定义没有随 CDB 架构调整,且 11→12 之间GV$FILESPACE_USAGE的FLAG列标识方式变了,原有的 UNDO 识别逻辑失效。

结论:用它做全库初筛可以,做精确判断和高风险表空间细化时,直接查DBA_DATA_FILES+DBA_FREE_SPACE自己算。


三、临时表空间和 UNDO:不能套用普通表空间的逻辑

这是另一个高频误区。三类表空间的"满"含义完全不同:

1)临时表空间使用率接近 100%,通常是正常的

Oracle 7.3 以后,临时表空间使用率长期贴近 100% 是常态,不能仅凭这个数字判断系统异常。真要看临时段占用,用V$SORT_USAGE/V$TEMPSEG_USAGE去定位是哪个会话、哪种段类型(排序、Hash Join、临时表、LOB)在占。

⚠️不要用V$TEMP_SPACE_HEADER/GV$TEMP_SPACE_HEADER算临时表空间使用率——它反映的是历史峰值的初始化块数,不是当前实际分配,会得出错误结果。

2)UNDO 看的是 OOS,不是简单百分比

UNDO 表空间不足,最直接的信号在 AWR 的 UNDO 统计里——OOS(Out Of Space)计数不为零,就说明 UNDO 空间不足。配合V$UNDOSTAT观察 undo 段分配与偷窃情况,比单纯看使用率靠谱得多。

3)MAXBYTES = 0的含义

在DBA_DATA_FILES里,MAXBYTES = 0表示该文件没有设置扩展上限(而不是"不能扩展"),别把它当成 0 容量去算。


四、智能扩容的关键:先判断"往哪里扩"

表空间满了要扩容,但扩到错误的地方,后果比不扩还严重。

最典型的事故:RAC 环境下,有人看到表空间 90%,不看数据文件路径,直接ADD DATAFILE,结果文件落到了某个节点的本地$ORACLE_HOME或本地目录。接下来就是跨实例访问失败、重启后节点起不来、还要做数据文件搬迁……一个"顺手扩容"变成一场故障。

同类问题在单机上也存在:文件被加到默认 HOME、加到莫名路径,后续管理一团乱。

所以自动扩容脚本里,第一件事不是ALTER TABLESPACE,而是识别存储类型。判断方法很简单——ASM 数据文件名以+开头:

  • +DATA/ORCL/DATAFILE/users.xxx.dbf→ ASM,磁盘组名取第一个/前的部分
  • /oradata/orcl/users01.dbf→ 文件系统,取dirname作为目录

两者后续处理完全不同:

存储类型剩余空间怎么取怎么加数据文件
文件系统df -Pm <目录>取第 4 列拼出完整路径 + 文件名
ASM查v$asm_diskgroup的usable_file_mb只给+磁盘组,文件名由 ASM 自动生成

一个存储感知的扩容判断流程

表空间真实使用率 > 90% │ ├─ 取该表空间任一数据文件路径 │ ├─ 以 '+' 开头? ── 是 ─→ ASM:查 usable_file_mb │ └─ 否 ─→ FS:df -Pm 目录 │ ├─ 剩余空间 < 安全水位? ── 是 ─→ 只告警,不扩容(避免撑爆存储) │ └─ 否 ─→ ADD DATAFILE(ASM 只给磁盘组,FS 拼路径+时间戳命名) │ └─ 输出含 ORA-? ─→ 失败告警;否则成功通知

核心脚本骨架

#!/bin/bash#=============================================================# oracle_tablespace_guard.sh# 表空间真实使用率检测 + 存储感知的智能扩容 + 告警# 适用: Oracle 11g / 12c / 19c / 21c / 23ai# 注意: 仅供参考,生产使用前请充分测试#=============================================================exportORACLE_HOME=/u01/app/oracle/product/19.0.0/dbhome_1exportORACLE_SID=ORCLexportPATH=$ORACLE_HOME/bin:$PATHDB_USER="/ as sysdba"ALERT_THRESHOLD=80# 仅告警AUTOADD_THRESHOLD=90# 自动扩容ADD_SIZE_MB=2048# 每次新增大小ADD_MAX_MB=32768# 新文件扩展上限ADD_NEXT_MB=256# 扩展步长SAFE_FREE_MB=5120# 存储安全水位,低于则只告警LOGDIR=/home/oracle/scripts/logLOGFILE=$LOGDIR/ts_guard_$(date+%Y%m%d).logDINGTALK_URL=""# 钉钉机器人 WebhookMAIL_TO=""# 告警邮箱mkdir-p"$LOGDIR"log(){echo"[$(date'+%F %T')]$1"|tee-a"$LOGFILE";}notify(){[-n"$DINGTALK_URL"]&&curl-s-H'Content-Type: application/json'\-d"{\"msgtype\":\"text\",\"text\":{\"content\":\"$1\"}}""$DINGTALK_URL">/dev/null;}log"===== 表空间巡检开始 ====="# 1) 采集真实使用率:表空间|已分配|已用|可扩展上限|真实使用率TS_LIST=$($ORACLE_HOME/bin/sqlplus-S"$DB_USER"<<'EOF'SET PAGESIZE0FEEDBACK OFF HEADING OFF LINESIZE400TRIMSPOOL ON SELECT df.tablespace_name||'|'||ROUND(SUM(df.bytes)/1024/1024,2)||'|'||ROUND((SUM(df.bytes)-NVL(fs.free_bytes,0))/1024/1024,2)||'|'||ROUND(SUM(CASE WHENdf.autoextensible='YES'THEN df.maxbytes ELSE df.bytes END)/1024/1024,2)||'|'||ROUND((SUM(df.bytes)-NVL(fs.free_bytes,0))/ SUM(CASE WHENdf.autoextensible='YES'THEN df.maxbytes ELSE df.bytes END)*100,2)FROM dba_data_files df,(SELECT tablespace_name, SUM(bytes)free_bytes FROM dba_free_space GROUP BY tablespace_name)fs WHERE df.tablespace_name=fs.tablespace_name(+)GROUP BY df.tablespace_name, fs.free_bytes ORDER BY5DESC;EXIT EOF)[-z"$TS_LIST"]&&{log"ERROR: 采集失败,请检查数据库连接";notify"【Oracle】$ORACLE_SID表空间巡检失败:连接异常";exit1;}echo"$TS_LIST"|whileIFS='|'read-rTS ALLOC USED MAXMB PCT;do[-z"$TS"]&&continuelog"表空间[$TS] 已分配=${ALLOC}MB 已用=${USED}MB 上限=${MAXMB}MB 真实使用率=${PCT}%"PCT_INT=${PCT%.*}if["${PCT_INT:-0}"-ge"$AUTOADD_THRESHOLD"];then# 2) 取存储位置LOC=$($ORACLE_HOME/bin/sqlplus-S"$DB_USER"<<EOF SET PAGESIZE 0 FEEDBACK OFF HEADING OFF LINESIZE 400 TRIMSPOOL ON SELECT file_name FROM dba_data_files WHERE tablespace_name='$TS' AND ROWNUM=1; EXIT EOF)LOC=$(echo"$LOC"|head-1|tr-d' ')ifecho"$LOC"|grep-q'^+';then# ---- ASM ----DG=$(echo"$LOC"|cut-d'/'-f1|tr-d'+')FREE=$($ORACLE_HOME/bin/sqlplus-S"$DB_USER"<<EOF SET PAGESIZE0FEEDBACK OFF HEADING OFF LINESIZE200TRIMSPOOL ON SELECT NVL(MIN(usable_file_mb),0)FROM v\$asm_diskgroupWHEREname='$DG';EXIT EOF)FREE=$(echo"$FREE"|head-1|tr-d' ')CLAUSE="'+$DG' SIZE${ADD_SIZE_MB}M AUTOEXTEND ON NEXT${ADD_NEXT_MB}M MAXSIZE${ADD_MAX_MB}M"TARGET="+$DG"else# ---- 文件系统 ----DIR=$(dirname"$LOC")FREE=$(df-Pm"$DIR"|awk'NR==2{print $4}')NEWFILE="${DIR}/${TS,,}_$(date+%Y%m%d%H%M%S).dbf"CLAUSE="'$NEWFILE' SIZE${ADD_SIZE_MB}M AUTOEXTEND ON NEXT${ADD_NEXT_MB}M MAXSIZE${ADD_MAX_MB}M"TARGET="$DIR"fiif["${FREE:-0}"-lt"$SAFE_FREE_MB"];thenlog"WARN: [$TS] 所在存储$TARGET剩余${FREE}MB < 安全水位${SAFE_FREE_MB}MB,跳过自动扩容"notify"【Oracle告警】$ORACLE_SID[$TS] 使用率${PCT}%,但$TARGET仅剩${FREE}MB,需人工介入"continuefilog"执行: ALTER TABLESPACE$TSADD DATAFILE$CLAUSE"RES=$($ORACLE_HOME/bin/sqlplus-S"$DB_USER"<<EOF SET PAGESIZE 0 FEEDBACK OFF HEADING OFF LINESIZE 400 ALTER TABLESPACE$TSADD DATAFILE$CLAUSE; EXIT EOF)ifecho"$RES"|grep-qi'ORA-';thenlog"ERROR: [$TS] 扩容失败:$RES"notify"【Oracle告警】$ORACLE_SID[$TS] 自动扩容失败:$RES"elselog"OK: [$TS] 扩容成功(+${ADD_SIZE_MB}MB)"notify"【Oracle通知】$ORACLE_SID[$TS] 使用率${PCT}%,已自动扩容${ADD_SIZE_MB}MB"fielif["${PCT_INT:-0}"-ge"$ALERT_THRESHOLD"];thenlog"WARN: [$TS] 使用率${PCT}% 超过告警阈值${ALERT_THRESHOLD}%"notify"【Oracle告警】$ORACLE_SID[$TS] 真实使用率${PCT}%,请关注"fidonelog"===== 表空间巡检结束 ====="

脚本中ORACLE_HOME、ORACLE_SID、Webhook、邮箱均为占位值;生产使用前请按实际环境替换并在测试环境验证。

几个设计取舍说明:

  • 用usable_file_mb而不是free_mb判断 ASM 能否扩容(下一节详述)。
  • 存储低于安全水位就只告警、不动手——把存储撑爆,比表空间满更难收拾。
  • 文件系统新文件名带时间戳,天然避免重名冲突;ASM 则由 ASM 自动命名。
  • 失败必须告警,不能让ORA-被静默吞掉。

五、ASM 磁盘组:表空间满了要扩,磁盘组满了更棘手

5.1 容量查询:free_mb和usable_file_mb不是一回事

SELECTname,ROUND(total_mb/1024,2)AStotal_gb,ROUND(free_mb/1024,2)ASfree_gb,ROUND((total_mb-free_mb)/total_mb*100,2)ASused_pct,ROUND(usable_file_mb/1024,2)ASusable_file_gbFROMv$asm_diskgroupORDERBYused_pctDESC;
  • free_mb:物理剩余。
  • usable_file_mb:考虑冗余度(normal/high)后真正能放文件的可用空间。

做扩容判断时看usable_file_mb。normal 冗余下,free_mb看着挺多,usable_file_mb可能只有一半。这两个数搞混,是"以为能扩、结果扩不动"的常见原因。

两个已知的坑:

  1. 从数据库实例查v$asm_diskgroup,某些老版本(10gR1)free_mb返回 0,这是为减少实例间消息传递的设计行为,10gR2 起改变。稳妥做法是连ASM 实例查,或用asmcmd lsdg。
  2. 未挂载磁盘组的TOTAL_MB可能显示异常(把未挂载磁盘组的总和算了进来,内部 Bug)。做容量统计前,先确认磁盘组状态是MOUNTED。

5.2 asmcmd:日常运维的好帮手

asmcmd lsdg# 各磁盘组容量/状态asmcmdls-l+DATA# 磁盘组内文件asmcmddu+DATA# 目录占用统计asmcmd lsop# 12c+ 查看 rebalance 等操作进度asmcmd--inst+ASM2# 12c+ 从任意节点连指定 ASM 实例

5.3 rebalance:加盘减盘后别急着走人

-- 监控进度SELECTgroup_number,operation,state,power,est_minutesFROMv$asm_operation;-- 调整力度(越大越快,对业务 I/O 冲击越大)ALTERDISKGROUPDATAREBALANCE POWER5;

生产建议在业务低峰期做,边做边看v$asm_operation。大磁盘组(几十上百块盘)在极端情况下会遇到 partner slot 达到结构上限、rebalance 起不来的问题,需要 offline/online 相关磁盘绕过——这属于较深的坑,遇到时不要盲目drop force。

5.4 磁盘组快满时的应急动作

当 ASM 磁盘组空间严重不足时,第一步是先关掉数据文件的自动扩展,防止文件自动增长把最后的空间也吃光:

-- 单个文件ALTERDATABASEDATAFILE'+DATA/ORCL/DATAFILE/users.xxx.dbf'AUTOEXTENDOFF;

可以先用DBA_DATA_FILES查出所有AUTOEXTENSIBLE='YES'的文件,批量生成autoextend off语句,先把局面稳住,再规划扩容或加盘。


六、从 11g 到 23ai/26ai:和监控/扩容相关的几个变化

  1. ASMCMD 能力持续增强:12c 起新增lsop(直接看 rebalance 进度)、--inst(跨节点连 ASM 实例)、pwmove(在线移动密码文件)等,多节点 ASM 管理方便很多。
  2. RAC 路径约束始终存在:无论哪个版本,RAC 下数据文件必须在共享存储。dbca静默建库时若漏了-datafileDestination +DATA -storageType ASM -diskGroupName DATA,数据文件和 spfile 会落到本地文件系统,直接导致远程节点启动失败。
  3. bigfile vs smallfile 的上限差异要记住:2^22-1块的结构上限针对 smallfile;bigfile 单文件可远大于此,这也是大库常选 bigfile 的原因之一。
  4. 监控视图本身的版本差异:如前面提到的DBA_TABLESPACE_USAGE_METRICS在 CDB/UNDO 场景的偏差,跨版本迁移监控脚本时要重新验证。

七、收尾:自动化的边界

最后强调几点,也是整篇文章最想传递的:

  1. 先把"分母"算对,再谈阈值。基于"已分配空间"的使用率是假指标,会制造大量误报,让人对告警麻木。
  2. 容量上限是三层约束的较小值:文件MAXBYTES、引擎结构上限、底层存储剩余。跨存储时逐文件算,别加总。
  3. 自动扩容前必须识别存储类型。RAC 下把数据文件加到本地路径,是比表空间满更严重的事故。
  4. ASM 看usable_file_mb,文件系统看df,两者不可混用。
  5. 监控与扩容可以拆开。保守团队只用监控、扩容留人工确认,本身就是一种合理的风控。
  6. 先测试,再生产。阈值、路径、命名规则都要按环境替换验证。

把这几件事做对,凌晨三点的电话会少很多。


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询