简介:这份文档面向 Oracle 数据库管理员与运维人员,聚焦调整 SGA 参数后实例无法正常启动的应急排错场景,适合需要快速定位 spfile 异常并恢复数据库的初中级 DBA 参考。压缩包仅含 1 个 docx 文件,约 14KB,篇幅短小但围绕故障处理组织内容。文档以一次实际恢复过程为线索,先说明如何通过指定 pfile 应急启动实例,再给出由 pfile 重建 spfile、修改 PFILE 中错误 SGA 参数,以及备份并回滚 dbs 目录下 spfileSID.ora 三种处理路径,同时保留 startup、show parameters 等命令输出,便于对照判断问题根源。已有 328 人学习,可作为 SGA 调整前后的检查清单和现场恢复思路参考。
1. 改完 SGA 起不来:八成不是内存不够,是参数在打架
凌晨两点把sga_target从 396M 调到 2G,一条alter system回车下去,shutdown之后startup就再也没回来,ORA-01078、LRM-00109、ORA-00838 轮着刷屏。这是 oracle 改 sga 异常里最常见的一幕:不是物理内存真的不够,而是 spfile 里那串二进制参数被写成了一组互相矛盾的数,实例在 nomount 阶段读参数时就被挡在门外,连 pmon 都拉不起来。
这份处理记录拆的就是这种场面:pfile 藏在哪个目录、怎么用startup pfile=把库先救活、怎么从 pfile 反建 spfile、改参数的三条约束线在哪、以及原文给的三个方法为什么顺序不能乱。适合手上管着单机或 RAC 节点的运维和 DBA,也适合刚接手一套老库、连$ORACLE_HOME/dbs里哪个文件管什么还没摸清的人。
2. 摸清加载链路:startup 到底读了 dbs 目录下哪个文件
2.1 三选一的加载顺序,决定了你改的是不是真正生效的那份
Linux 上敲下startup之后,实例在 nomount 阶段会去$ORACLE_HOME/dbs目录里按固定顺序找参数文件:先找spfile$ORACLE_SID.ora,找不到再找spfile.ora,再找不到才退到init$ORACLE_SID.ora。三份都找不到,就是 ORA-01078 加 LRM-00109 这一对经典组合。这个顺序很关键——很多环境里initORCL.ora和spfileORCL.ora同时存在,你以为自己在改文本文件,实际上实例读的是二进制那份,改了等于没改。
spfile 是二进制格式,只能用ALTER SYSTEM SET ... SCOPE=SPFILE或CREATE SPFILE这类语句写入,不能拿 vi 去编辑;pfile 是纯文本,create pfile from spfile导出来的就是它。改 SGA 出问题,绝大多数是这么来的:一条alter system set sga_target=2G scope=spfile把值写进了 spfile,重启时实例读到sga_target大于sga_max_size,或者memory_target小于sga_max_size,直接在 nomount 卡死。此时数据库还没走到 mount,任何数据字典都查不了,只能靠外部文件把实例救回来。
原文里那份admin/SID/pfile/init.ora.8282011115435,就是 dbca 建库或者某次CREATE PFILE留下的带时间戳快照。这类文件通常散落在$ORACLE_BASE/admin/$ORACLE_SID/pfile/下,一份库可能躺着几十个,文件名里的数字是生成时刻,不是版本号。它的价值在于:它是文本,能看能改,而且大概率保存着改坏之前的那套参数。
2.2 用告警日志和文件时间戳锁定「案发前那一份」
别急着startup pfile=,先花两分钟确认现场。第一步确认实例真的没起来,ps -ef里看不到ora_pmon_进程就是彻底停了;第二步翻告警日志,最后几十行一定写着它为什么拒绝启动;第三步按时间排序找出最近几份 pfile 候选。
# 环境变量必须先对齐,多实例机器上搞错 SID 是最低级的翻车 export ORACLE_SID=ORCL export ORACLE_HOME=/u01/app/oracle/product/11.2.0/dbhome_1 export PATH=$ORACLE_HOME/bin:$PATH # 确认 pmon 是否还在,有输出说明实例还活着,处理思路完全不同 ps -ef | grep -v grep | grep "ora_pmon_${ORACLE_SID}" # 按修改时间倒序列出 pfile 目录,最近的几份就是候选 ls -lt $ORACLE_BASE/admin/$ORACLE_SID/pfile/ | head -20ls -lt的输出里重点看两列:修改时间和文件大小。时间戳文件名和真实 mtime 对不上的情况很常见——有人拷贝过文件,mtime 会变成拷贝时间。所以宁可多挑两三份出来对比内容,也不要盲信文件名里那串数字。
告警日志的位置分版本:11g 及以后在$ORACLE_BASE/diag/rdbms/$ORACLE_SID/$ORACLE_SID/trace/alert_$ORACLE_SID.log,10g 在$ORACLE_BASE/admin/$ORACLE_SID/bdump/下。日志动辄几百兆,别整篇翻,用 grep 精准捞。
ALERT=$ORACLE_BASE/diag/rdbms/$ORACLE_SID/$ORACLE_SID/trace/alert_$ORACLE_SID.log # 捞出启动失败前后的关键行:报错码 + SGA 分配结果 grep -nE "ORA-[0-9]{5}|LRM-[0-9]{5}|System Global Area|Starting ORACLE instance" "$ALERT" | tail -60这段输出的读法是:先找最后一条Starting ORACLE instance (normal),它后面紧跟的报错就是本次失败的真正原因;再往上翻,找到上一次成功启动时打印的Total System Global Area,那是出事前正常工作的内存规模,重建 pfile 时拿它做基准最靠谱。原文里成功启动后打印的 612368384 bytes 和 mount 状态下的 415236096 bytes 差了将近 200M,这个差值本身就说明 pfile 和 spfile 里存的不是同一套参数,后面第 4 章会专门讲这个差异从哪来。
2.3 三个方法的适用边界,别上来就选最顺手那个
原文给了三条路,很多人看一眼就选方法一,结果在某些场景下反而多绕一圈。先把边界划清楚再动手。
方法一,用 pfile 启动再反建 spfile,前提是这份 pfile 里的参数本身是合法的。它快,一条startup pfile=就能验证,但如果你手上这份 pfile 是「改完之后才导出的」,那它里面同样带着错误参数,照样起不来,只是报错从读 spfile 变成读 pfile。
方法二,直接改 pfile 里的错误参数,适合方法一试出来还报 ORA-00823、ORA-00838 这类参数冲突的场景。它的好处是你能明确知道自己在改哪个值,坏处是需要你对sga_max_size、sga_target、memory_target三者的约束关系心里有数,否则改完还是错的。
方法三,恢复$ORACLE_HOME/dbs下的 spfile 备份,是唯一能在几十秒内回到原点的方案,但它严格来说不是「出事后的办法」,而是「出事前就该做的动作」。没备份的时候,这条路的变通做法是从告警日志里抄回上一次成功启动的参数值,手工拼一份 pfile——能救,但慢。
先看 pfile 内容再决定走哪条,这一步不能省:
# 只看和内存相关的行,忽略那些注释和无关参数 grep -inE "sga_max_size|sga_target|memory_target|memory_max_target|pga_aggregate_target|db_cache_size|shared_pool_size|large_pool_size|java_pool_size|lock_sga|pre_page_sga|use_large_pages" \ $ORACLE_BASE/admin/$ORACLE_SID/pfile/init.ora.8282011115435输出里如果看到*.sga_target=2147483648而*.sga_max_size=415236096,那就是典型的「目标值超过天花板」,方法一直接走不通,得先用方法二把sga_max_size提上去或者把sga_target压回来。pfile 里的参数可能带*.前缀也可能裸写,grep 的时候别加锚点,宽松匹配更保险。
3. 用 pfile 应急启动:三条命令把实例从 nomount 拉回 open
3.1 先启动到 open,确认 SGA 分配是否合理
确认完 pfile 内容没问题,就可以拉了。这一步的判断标准很直接:实例能走到「数据库已经打开」,说明 pfile 里的参数组合至少能过 nomount 和 mount 两道关。走不到,说明 pfile 本身带着硬伤,退回方法二。
-- 用显式 pfile 路径启动,绕开 dbs 目录下那份坏掉的 spfile SQL> startup pfile='/u01/app/oracle/admin/ORCL/pfile/init.ora.8282011115435'; ORACLE 例程已经启动。 Total System Global Area 612368384 bytes Fixed Size 1250428 bytes Variable Size 167775108 bytes Database Buffers 436207616 bytes Redo Buffers 7135232 bytes 数据库装载完毕。 数据库已经打开。startup pfile=的参数是绝对路径,必须能读到,且 oracle 用户有读权限。路径里不要出现 shell 通配符或引号嵌套,中文环境下的单引号问题尤其容易踩。起来之后第一件事不是急着建 spfile,而是核对这段Total System Global Area和你预期的差多少。原文这里显示 612M,比 mount 状态下的 415M 大了将近 200M,多出来的部分通常是自动分配的 buffer cache 和 shared pool——sga_target打开时,Oracle 会按内部算法把 SGA 各组件撑到一个它认为合理的比例,所以「目标值」和「实际分配值」本来就不会完全相等。
-- 看实际分配到了哪些组件,别只看总数 SQL> select name, round(bytes/1024/1024, 1) as mb, resizeable 2 from v$sgainfo order by bytes desc; -- 看 pfile 里的值和当前生效值是否一致 SQL> show parameter sga; SQL> show parameter memory_target;v$sgainfo里resizeable为 YES 的组件才受sga_target动态调度,Fixed Size 和 Redo Buffers 是钉死的。如果Redo Buffers明显偏小(比如只有几百 K),说明log_buffer没设或者被压过,重建 spfile 之前顺手把它补回去比较省事。
3.2 备份旧 spfile 再反建,别在原地覆盖
原文的顺序是startup pfile→create spfile from pfile→shutdown→startup mount。这个流程能跑通,但有一个隐患:create spfile在不带路径时,默认写到$ORACLE_HOME/dbs/spfile$ORACLE_SID.ora,如果那个位置已经有一份坏掉的 spfile,行为取决于版本和实例状态,运气不好就是覆盖或者报 ORA-32002(cannot create SPFILE already in use by the instance)。多花十秒钟把它挪走,比事后找后悔药便宜得多。
# 给坏掉的 spfile 打上时间戳后挪走,保留现场 cd $ORACLE_HOME/dbs mv spfile${ORACLE_SID}.ora spfile${ORACLE_SID}.ora.bad.$(date +%Y%m%d%H%M) ls -l spfile${ORACLE_SID}*这一步在实例用 pfile 启动的状态下做是安全的,因为 spfile 当前根本没被打开。文件名后缀带上分钟级时间戳,是为了同一天出两次事的时候还分得清哪份是哪份。
-- 从 pfile 生成一份全新的 spfile,写到默认路径 SQL> create spfile from pfile='/u01/app/oracle/admin/ORCL/pfile/init.ora.8282011115435'; 文件已创建。也可以显式指定输出路径:create spfile='/u01/app/oracle/product/11.2.0/dbhome_1/dbs/spfileORCL.ora' from pfile='...'。显式写路径的好处是你能一眼确认落在哪;坏处是路径写错就报 ORA-27040 之类的文件创建错误。用默认路径 + 先挪旧文件,是我更习惯的组合。
创建完立刻验证文件存在且大小合理,spfile 一般就几十 K,如果只有 0 字节,说明写的时候出了问题。
ls -l $ORACLE_HOME/dbs/spfile${ORACLE_SID}.ora # 只读方式瞄一眼 spfile 里的参数名,确认关键项都在(不要编辑) strings $ORACLE_HOME/dbs/spfile${ORACLE_SID}.ora | grep -iE "sga_target|sga_max_size|memory_target"strings只是用来确认参数被写进去了,spfile 的二进制格式带校验和,手工用十六进制编辑器改一个字节都可能让整个文件失效,这个念头直接掐掉。
3.3 用 spfile 重启一遍,才算真的修好
create spfile成功不等于问题解决,必须完整走一次shutdown+startup,让实例从 spfile 读参数。这一步是验证,不是走过场——很多参数问题在 pfile 启动时被掩盖,只有走 spfile 路径才会暴露。
SQL> shutdown immediate; 数据库已经关闭。 已经卸载数据库。 ORACLE 例程已经关闭。 SQL> startup; ORACLE 例程已经启动。 Total System Global Area 415236096 bytes Fixed Size 1249296 bytes Variable Size 121638896 bytes Database Buffers 285212672 bytes Redo Buffers 7135232 bytes 数据库装载完毕。 数据库已经打开。起来之后确认实例到底在用哪份文件:
SQL> show parameter spfile; NAME TYPE VALUE --------- ----------- ------------------------------ spfile string /u01/app/oracle/product/11.2.0/dbhome_1/dbs/spfileORCL.oraVALUE非空,说明实例走的是 spfile 路径,修复闭环完成。如果这一列是空的,说明它退回去读了init$ORACLE_SID.ora,那就要检查 dbs 目录下是不是还留着一份优先级更高的 pfile 在捣乱。
原文在这一步之后还查了show parameter sga,输出sga_max_size=396M、sga_target=396M。这两个值一样大,是 ASMM 的典型配置:天花板和目标值对齐,SGA 组件在这个总额里动态伸缩。如果你希望 SGA 能在线扩缩,sga_max_size要留出余量;如果追求稳定,两者相等也没问题,只是以后想在线加大sga_target会被天花板挡住,得再重启一次。
3.4 把参数落盘情况查清楚,避免下次重启又回到原点
修好之后补一刀检查:当前生效值和 spfile 里存的值是不是同一套。这两者不一致,意味着下次重启会变脸,属于典型的「今天好了明天又炸」。
-- 生效值 SQL> show parameter sga_target; -- spfile 里存的值,isspecified=TRUE 表示显式写在文件里的项 SQL> select name, value, isspecified 2 from v$spparameter 3 where name in ('sga_target','sga_max_size','memory_target','pga_aggregate_target') 4 and isspecified = 'TRUE';v$spparameter的value列如果显示的是原始字节数而不是带单位的字符串,别慌,这是正常现象——它记录的是字节值。把两边对照一遍,一致就收工,不一致就先查清楚是哪条alter system用了scope=memory没落盘。
4. 参数边界:sga_max_size、sga_target、memory_target 谁管谁
4.1 天花板和目标值:先抬上限,再调目标
sga_max_size是 SGA 能长到的最大尺寸,静态参数,改完必须重启才生效。sga_target是 ASMM 的调度目标,动态参数,可以在线改,但有一个硬约束:不能超过sga_max_size。这一条就是大量 ORA-00823(Specified value of sga_target greater than sga_max_size)的来源,也是原文那次事故最可能的直接原因——把目标值调大了,天花板没动。
正确的改法顺序是先抬上限、再调目标,两条都写进 spfile,一次重启解决:
-- 第一步:先抬天花板,静态参数只能写 spfile SQL> alter system set sga_max_size=2G scope=spfile; -- 第二步:再设目标值,写 spfile,等重启后生效 SQL> alter system set sga_target=2G scope=spfile; -- 第三步:重启验证 SQL> shutdown immediate; SQL> startup;反过来先执行alter system set sga_target=2G scope=both,在sga_max_size还是 396M 的库上会当场报 ORA-00823,运气好只是语句失败,运气不好就是参数写了一半、库处于半改状态。这种「改一半」的现场最难受,因为 spfile 里的值已经和新目标对不上了。
还有一个细节容易被忽略:SGA 的分配粒度是 granule,小于 1G 的 SGA 通常是 4M 一个粒度,1G 到 8G 之间是 16M,更大是 32M(不同平台和版本略有差异)。所以你把sga_target设成 1000M,实际分配可能是 1008M 或者 992M,这不是 bug,是向上或向下对齐到 granule 倍数。真要精确控制,先查一下粒度:
SQL> select name, round(bytes/1024/1024, 1) as mb 2 from v$sgainfo where name = 'Granule Size';4.2 开了 memory_target,sga_target 就该退场
11g 引入的 AMM 用memory_target一个参数统管 SGA 和 PGA,配置上更省心,但它和 ASMM 是互斥路线。一旦memory_target设成非零值,再去单独设sga_target或pga_aggregate_target,就容易撞上 ORA-00824 这类冲突报错——参数之间打架,实例照样起不来。
切换方向要明确,不能两边都留着值:
-- 从 AMM 切回 ASMM:先关掉 memory_target,再设 sga_target SQL> alter system set memory_target=0 scope=spfile; SQL> alter system set sga_target=2G scope=spfile; SQL> alter system set pga_aggregate_target=512M scope=spfile; SQL> shutdown immediate; SQL> startup;反方向切换(ASMM 转 AMM)则是先设memory_max_target,再设memory_target,两个都要写 spfile,且memory_target不能超过memory_max_target。这个上限关系搞反,报的就是 ORA-00838(Specified value of MEMORY_TARGET is too small)——报错信息说「太小」,实际原因是它比sga_max_size还小,被判定为不合法。
Linux 上还有一道额外的坎:用 AMM 的时候,Oracle 会在/dev/shm上映射共享内存段,这个挂载点默认往往只有物理内存的一半,memory_target一开大就报 ORA-00845(MEMORY_TARGET not supported on this system)。查一下就知道:
df -h /dev/shm grep -i huge /proc/meminfo/dev/shm不够就调大挂载参数,或者干脆放弃 AMM 走 ASMM + HugePages,后者在生产环境更常见,因为大页能减少页表开销,内存越大收益越明显。但 HugePages 又是另一个坑,见下一章。
4.3 scope 的三个取值,决定了你的改动能活多久
ALTER SYSTEM SET的scope参数只有三个值,但每一个的含义差别很大,改 SGA 出问题的人里至少三分之一是栽在这上面。
| scope 取值 | 写入位置 | 生效时机 | 重启后是否保留 |
|---|---|---|---|
| MEMORY | 只改内存 | 立即 | 不保留,回到 spfile 里的值 |
| SPFILE | 只改 spfile | 重启后 | 保留 |
| BOTH | 内存和 spfile 都改 | 立即 | 保留 |
静态参数(sga_max_size、memory_max_target、log_buffer等)只能写 SPFILE,写 BOTH 会直接报 ORA-02095。动态参数三种都能用。最坑的场景是:某次调优临时用scope=memory把sga_target调大测了一下,觉得效果不错就收工了,结果下次重启回到老值,业务方以为你根本没改。
我的习惯是默认写scope=both,只在明确知道这是静态参数或者只想临时验证时才换。改完当天必须安排一次重启验证,别等下次计划内维护才暴露问题——那时候业务方已经在盯着了。
5. 避坑与排查:五条把库改停机的真实记录
5.1 ORA-32002:旧 spfile 还在,就去建新的
现象:create spfile from pfile=...报 ORA-32002,提示 spfile 正被实例使用。
原因:实例如果是用 spfile 启动的,那个文件处于打开状态,不能覆盖。虽然用startup pfile=启动后一般不会撞上,但有些人为了省事直接在原实例上操作,就踩了。
解决:先shutdown immediate,确认 pmon 进程消失,再把旧 spfile 改名挪走(改名比删除好,留证据),然后重新startup pfile=到 nomount 或 open 状态,再执行create spfile。顺序记牢:挪文件 → 用 pfile 起 → 建 spfile → 重启。
5.2 startup pfile 还是报参数冲突:pfile 本身就是坏的
现象:按方法一操作,startup pfile=依然报 ORA-00823 或 ORA-00838,实例还是起不来。
原因:这份 pfile 不是「改坏之前」的快照,而是「改坏之后」导出的,或者干脆是别人手工拼的。文件名带时间戳不代表内容正确,ls -lt排第一也不代表能用。
解决:打开 pfile 用 grep 看那几个 SGA 参数,把sga_target压到不超过sga_max_size,把memory_target提到不小于sga_max_size,再试。挑候选文件时多挑两三份对比,找那份Total System Global Area和你记忆中的正常值对得上的。实在找不到,就从告警日志里翻上一次成功启动打印的 SGA 分配值,手工拼一份最小可用的 pfile——只留sga_target、pga_aggregate_target、db_name、control_files、db_block_size这几项,先起来再说。
5.3 ORA-00845:/dev/shm 装不下你设的 memory_target
现象:改用 AMM 之后启动失败,报 ORA-00845: MEMORY_TARGET not supported on this system。
原因:Linux 上 AMM 依赖/dev/shm,这个 tmpfs 默认大小常常是物理内存的一半,memory_target设得比它还大就起不来。
解决:df -h /dev/shm看一眼实际容量,要么调大挂载(改/etc/fstab里的size=参数后重新挂载),要么把memory_target降到安全范围内。生产环境我更倾向直接放弃 AMM,走 ASMM 加 HugePages,稳定性和可观测性都更好。
5.4 ORA-27102:HugePages 没配够,use_large_pages=ONLY 直接把门焊死
现象:参数看着都对,sga_max_size也没超物理内存,但实例就是起不来,报 ORA-27102: out of memory。
原因:pfile 或 spfile 里设了use_large_pages=ONLY,要求 SGA 全部走大页,但系统上vm.nr_hugepages配的数量不够支撑这个 SGA 大小。Oracle 不会退而求其次用小页,直接失败。
解决:grep HugePages /proc/meminfo看HugePages_Total和HugePages_Free,按 SGA 大小除以单页大小(一般是 2M)算出需要多少页,用sysctl vm.nr_hugepages=N调整并写进/etc/sysctl.conf。应急情况下先把use_large_pages改成FALSE或从 pfile 里注释掉,让实例先起来,再从容配大页。
5.5 改完当时挺好,下次重启回到老值
现象:调完 SGA 观察了一周都正常,某次计划内重启后又变回原来的内存配置。
原因:alter system用了scope=memory,只改了内存没写 spfile。当时show parameter看着是新值,一切正常,重启就露馅。
解决:改完立刻用v$spparameter核对 spfile 里的值,两边不一致就补一条scope=spfile的语句。更省事的做法是养成习惯:只要不是临时验证,一律scope=both;静态参数改完在变更单上标注「需重启验证」,别拖到下一次维护窗口。
6. 回滚预案:把 spfile 备份和恢复演练做成固定动作
上面五条坑,四条都能靠同一个动作绕过:改之前留备份。但备份本身也有讲究——光cp一份 spfile 不够,因为你不一定记得住当时改了什么,恢复回来也只能回到「上一次改完」而不是「上一次改之前」。
我现在固定做三件事,加起来不到半分钟:导一份当前生效参数的 pfile 文本、复制一份 spfile 二进制、把两份文件按日期归档。
# 1. 从 spfile 导出可读的 pfile 快照,带日期命名 sqlplus -S / as sysdba <<'EOF' create pfile='/home/oracle/bak/init_ORCL_$(date +%Y%m%d).ora' from spfile; exit EOF # 2. 复制 spfile 本体,保留二进制原样 cp -p $ORACLE_HOME/dbs/spfile${ORACLE_SID}.ora \ /home/oracle/bak/spfile${ORACLE_SID}.ora.$(date +%Y%m%d%H%M) # 3. 顺手记下当前 SGA 分配,出问题时拿来对照 sqlplus -S / as sysdba <<'EOF' select name, round(bytes/1024/1024,1) mb from v$sgainfo order by bytes desc; exit EOF注意第一段 heredoc 里的$(date ...)在单引号包裹的<<'EOF'里不会展开,要么把 EOF 的引号去掉,要么直接在 SQL 里写死路径。这个细节我栽过,导出来的文件名里带着一串字面量美元符号。
备份策略按变更频率定,不用搞太复杂:
| 时机 | 动作 | 保留 | 校验方式 |
|---|---|---|---|
| 每次改 SGA 前 | 导出 pfile + 复制 spfile | 至少 3 份 | show parameter spfile确认路径 |
| 每月例行 | 归档一次完整 dbs 目录 | 6 个月 | 抽查 spfile 能否strings出参数 |
| 大版本升级前 | 全库冷备 + dbs 目录打包 | 长期 | 在测试库做一次恢复演练 |
真正值钱的不是备份动作,是演练。我见过太多环境备份文件躺了一年,真出事的时候才发现路径写的是老机器的目录。每季度挑一个非生产库,把 spfile 删掉,走一遍「挪文件 → startup pfile → create spfile → 重启」,全程掐表,超过十分钟就说明流程里有环节没吃透。
判断修没修好,我只看两个信号:show parameter spfile有值且指向预期路径,v$spparameter里isspecified='TRUE'的那几项和show parameter的结果对得上。两个都对,才敢把工单关掉。
从那以后我每次敲alter system set ... scope=spfile之前,都会先把 pfile 导出来、spfile 复制一份,两个动作十秒钟,但能把一次凌晨的加班挡在门外。希望帮到你。
本文还有配套的精品资源,点击获取