
这套双节点11g RAC上周刚经历过一次SGA扩容整个过程说复杂不复杂但踩坑的点是真不少。刚开始只是业务方反馈高峰期偶尔出现ORA-04031AWR里Buffer Cache Hit Ratio也从99.2%一路滑到95.6%DB Time持续攀升。我当时的判断是Shared Pool压力过大但进一步分析后发现问题根源不是组件分配不均而是整个SGA的总预算已经不够业务用了。这就引出一个很多DBA会忽略的关键认知ASMM模式解决的是SGA内部组件之间的自动调剂但它调剂的前提是SGA_TARGET这个总预算本身充足总盘子不够的时候ASMM再怎么自动也白搭。这篇文章就把我在Oracle 11g RAC ASMM模式下调整SGA内存大小的完整实战过程记录下来包括调整前的体检、内存预算的计算、RAC下ALTER SYSTEM的正确姿势、以及这次过程中踩过的坑和最终的效果验证。如果你正准备动生产环境RAC的SGA这套流程和检查清单可以直接抄作业。1. 调整背景什么样的信号说明SGA该动了1.1 触发这次调整的具体现象先说当时的环境一套运行在Linux x86_64上的双节点Oracle 11.2.0.4 RAC数据库版本11g配置了ASMM自动共享内存管理SGA_TARGET和SGA_MAX_SIZE都是12GPGA_AGGREGATE_TARGET2G物理内存32G。业务侧反馈近两周业务高峰期经常出现类似下面的报错ORA-04031: unable to allocate 4096 bytes of shared memory (large pool,XML,pe,KGCS)ORA-04031这个错误一出现说明Shared Pool确实在某些时刻连小块的shared memory都分配不出来了。与此同时AWR报告里几个关键指标也在同步恶化Buffer Cache Hit Ratio从99.2%降到95.6%Top 5等待事件里出现buffer busy wait和gc buffer busy acquireDB Time在业务高峰时段明显上升和DB CPU的差距拉大这里有一个细节值得注意的是gc buffer busy acquire是RAC环境特有的全局缓存等待它往往意味着Buffer Cache容量不足导致跨节点频繁请求同一个数据块。单纯看Shared Pool报错可能第一反应是加大Shared Pool但实际上Buffer Cache也已经在告急了。两个组件同时压力大说明SGA总量本身不够了。1.2 ASMM模式下为什么还需要手动调SGA很多刚接触Oracle内存管理的人会对ASMM有一个误解以为开启了SGA_TARGET的自动管理之后每个组件的内存都能自动扩缩遇到压力会自动解决。但实际上ASMM的工作方式是这样的MMAN后台进程会周期性检查Buffer Cache、Shared Pool、Large Pool等组件的使用情况在SGA_TARGET总预算内动态地给组件增加或回收内存。换句话说ASMM做的是存量再分配而不是增量扩容。当SGA_TARGET本身只有12G而业务负载需要15G的内存时ASMM能做的只是在12G里来回倒腾Shared Pool紧张了就去Buffer Cache挤一点结果Buffer Cache开始出现命中率下降和gc buffer busy acquire。这种时候再怎么等它自动调节也不可能变出更多的总内存。打个比方ASMM就像家里的管家能在一笔固定预算里帮你精打细算分配各项开支但如果这个月总收入就是不够交学费和买菜管家再厉害也解决不了。这个时候必须由当家人决定增加总预算对应到Oracle里就是手动调整SGA_TARGET。所以在这次场景里目标很明确需要把SGA_TARGET从12G调大到20G同时因为目标值已经超过当前的SGA_MAX_SIZE12G还得同步调整SGA_MAX_SIZE——这就牵扯出后面要讲的一堆参数联动和重启问题。2. 调整前的体检先确认手上有哪些牌2.1 双节点SGA参数与ASMM状态核实动手之前我先把两边的配置彻底摸了一遍。RAC环境最怕的就是两个节点参数不一致所以第一步是在任意一个节点上通过GV$PARAMETER直接对比两个实例的参数值SELECT INST_ID, NAME, VALUE FROM GV$PARAMETER WHERE NAME IN (sga_max_size,sga_target,pga_aggregate_target,memory_target);查看结果时要重点确认几件事memory_target是否为0。如果memory_target不为0说明系统启用了AMM自动内存管理SGA_TARGET会退化成最小值语义你设置的SGA_TARGET不一定是你真正想要的目标值。11g RAC生产环境我强烈建议关闭AMM这个后面专门说。sga_target是否大于0。只有sga_target0ASMM才是真正启用的。sga_max_size和sga_target当前的值是否一致。实操中很多系统初始值是一致的也有不少场景MAX比TARGET大留了余量。再看一下ASMM模式下各组件的自动调节状态用这个SQLCOLUMN COMPONENT FORMAT A30 SELECT COMPONENT, CURRENT_SIZE/1024/1024 AS CURRENT_MB, MIN_SIZE/1024/1024 AS MIN_MB, USER_SPECIFIED_SIZE/1024/1024 AS USER_SPECIFIED_MB FROM V$SGA_DYNAMIC_COMPONENTS;这条查询里有几个字段对判断ASMM状态很关键USER_SPECIFIED_SIZE为0表示该组件完全由ASMM自动管理USER_SPECIFIED_SIZE不为0表示该组件被设置了最小下限ASMM调度时会优先保留这个下限值这次检查下来两个节点的参数完全一致组件都是完全自动状态没有历史遗留的手动设置这是个很好的起点。如果发现某些组件之前被人为设置了最小值后面调整的时候就需要格外谨慎因为SGA_TARGET新值必须大于所有组件下限之和否则会报错。2.2 系统内存、PGA与HugePages全景SGA不是独立存在的调整之前一定要把服务器的整体内存账算清楚。我在两个节点上都执行了free -g和top确认除了Oracle实例和监听之外还有没有其他吃内存的进程。这次服务器的32G内存是专库专用的相比之前压力小很多。同时检查了PGA和SGA的配比。11g的常规经验是OLTP系统里PGA和SGA的配比大概在1:4到1:6调SGA的时候不能只盯着SGA看PGA本身也要有足够的空间。这次PGA保持2G不变SGA加到20G后SGAPGA合计22G占物理内存的68.75%整体还在可接受的范围内。还有一个很多人容易遗漏的检查项是HugePages。双节点都执行了grep -i hugepages /proc/meminfo检查结果里HugePages_Total是6144HugePagesize是2048kB也就是2MB一页6144页总共对应12G左右。这个值刚好匹配原来的SGA12G的预期。调整SGA到20G之后HugePages是一定要跟着重新计算的不然实例可能直接起不来。这个问题我放在后面踩坑章节详细展开但调整前先把现状记录下来是非常必要的。2.3 记录调整前的组件基线调整前我特意把v$sga_dynamic_components的当前值、AWR里Buffer Cache Hit Ratio、Shared Pool相关的统计信息都做了截图存档。为什么这么做因为在ASMM模式下调整SGA_TARGET之后组件不会立刻全部变化Oracle会按需逐步调整。没有基线数据的话调整后你很难判断是调整到位了还是还需要等。基线记录的重点是Shared Pool当前实际大小Buffer Cache当前实际大小Large Pool当前值空闲的可动态调整内存记录完这些之后才能放心地进入内存预算规划阶段。3. 内存预算SGA调到多少合适3.1 SGA、PGA、系统内存三者怎么分确定SGA调大目标值最忌讳的就是拍脑袋随便选一个数字。我一般是按下面的顺序来计算先明确物理内存总量然后减去必须保留的系统内核、文件缓存、监听进程、操作系统其他服务需要的内存剩下的才是Oracle可用的内存池。在专库专用的服务器上SGAPGA通常可以占到物理内存的60%到80%。如果服务器上还跑着其他应用这个比例要往下调很多留足系统余量避免swap。以这台32G内存的服务器为例系统和其他进程预留约8G-10GPGA保持2GSGA可以安排到20GSGAPGA22G占物理内存约68.75%这个比例对于11g RAC的OLTP场景是合理的。如果再激进一点到24G甚至26G业务高峰期内存吃紧的话反而容易引发swap性能下降比命中率下降更可怕。3.2 SGA_TARGET与SGA_MAX_SIZE的关系这一步是整个调整中最容易出问题的参数联动SGA_MAX_SIZE和SGA_TARGET是两个完全不同的概念。SGA_MAX_SIZE是SGA可以增长到的物理上限类似天花板SGA_TARGET是ASMM自动管理的目标值类似期望工作值SGA_TARGET可以动态调整但前提是目标值不能超过当前的SGA_MAX_SIZE。一旦调整后的SGA_TARGET大于当前的SGA_MAX_SIZE必须先调大SGA_MAX_SIZE而这个参数在Oracle 11g里只接受SCOPESPFILE的修改方式意味着它必须重启实例才能生效。这次从12G调到20G明显超过了MAX12G的上限所以必须走完整链路先改SGA_MAX_SIZE为20G再改SGA_TARGET为20G两个参数都写进SPFILE然后滚动重启两个节点的实例。这里插一句如果只是想从12G调到10G这种调小操作而且SGA_MAX_SIZE不动那么直接ALTER SYSTEM SET SGA_TARGET10G SCOPEBOTH SID*就能立即生效不需要重启。调小和调大的处理逻辑差异很大后面章节会分开讲。3.3 组件下限参数要不要动ASMM模式下Buffer Cache、Shared Pool、Large Pool、Java Pool这些组件都可以通过设置各自的最小值参数DB_CACHE_SIZE、SHARED_POOL_SIZE、LARGE_POOL_SIZE、JAVA_POOL_SIZE来保留底线。这次调整我没有动任何组件下限全部保持0让ASMM完全自动调配。原因很简单在没有明确证据表明某个组件会长期被挤压到影响业务的情况下手动设下限反而会束缚ASMM的手脚。但有一种场景确实值得设置组件下限。比如历史AWR显示Shared Pool经常被压缩到很小导致parse频繁失败或ORA-04031而Buffer Cache又相对宽裕。这种情况下可以给SHARED_POOL_SIZE设一个最小值比如2G保证ASMM再怎么调度都不会把Shared Pool压到2G以下。设置下限时务必要注意所有组件下限之和必须小于SGA_TARGET否则实例会报错或者启动失败。另外下限值一旦设了ASMM自动调节的空间就小了Buffer Cache能借到的内存也少了这是需要综合权衡的。4. 动手调整RAC下ALTER SYSTEM的正确打开方式4.1 调大的完整链路SGA_MAX_SIZE先行明确了目标20G之后我在维护窗口执行了如下操作。核心原则是先把SGA_MAX_SIZE这个天花板抬高再设置SGA_TARGET目标值两个参数都写进SPFILE。在节点1上执行ALTER SYSTEM SET SGA_MAX_SIZE20G SCOPESPFILE SID*; ALTER SYSTEM SET SGA_TARGET20G SCOPESPFILE SID*;注意这里的两个关键点。第一个是SID*表示该参数修改对RAC所有实例生效。如果漏掉这个参数可能只会修改当前实例对应的参数值造成两个节点配置分叉后面详细讲。第二个是SCOPESPFILE因为SGA_MAX_SIZE不支持动态修改必须走SPFILE重启生效。你可能会有疑问为什么不先动态把SGA_TARGET改到12G以内比如先改成11G再改MAX为20G重启之后再动态把TARGET升到20G这样做理论上可行但完全没有必要而且增加了操作步骤和误操作概率。维护窗口时间足够的情况下一次性把两个参数都写进SPFILE重启后一步到位反而是最稳妥的。接下来滚动重启两个实例。所谓滚动重启就是一个节点一个节点地停启保证集群始终有一个节点对外提供服务srvctl stop instance -d racdb -i racdb1 srvctl start instance -d racdb -i racdb1节点1恢复并验证正常后再操作节点2srvctl stop instance -d racdb -i racdb2 srvctl start instance -d racdb -i racdb2使用srvctl而不是直接SQL shutdown immediate有两个好处一是srvctl会同步更新OCR中的实例状态集群层面的管理更规范二是通过srvctl启停实例会正确地通知集群资源的状态变化避免后续出现资源状态不一致的麻烦。11g RAC这里命令里的-d参数是数据库的DB_UNIQUE_NAME-i参数是实例名。4.2 调小时的操作BOTH就能动态生效这次虽然走的是调大链路但顺手把调小的场景也一并说清楚。如果你只是想把SGA_TARGET从12G调小到8G而且8G不超过SGA_MAX_SIZE那么根本不需要重启ALTER SYSTEM SET SGA_TARGET8G SCOPEBOTH SID*;这里SCOPEBOTH表示既写入SPFILE又立即对当前实例生效。执行之后Oracle会启动SGA内部的收缩流程MMAN后台进程逐步把各组件内存释放出来最终收敛到8G总量内。需要特别提醒的是收缩是渐进的、非实时的。你执行完命令后马上查v$sga_dynamic_components很可能看到各组件还在老大小不要慌。Oracle会在运行过程中根据负载逐步调整等业务低峰期再看通常几个小时内会收敛。如果业务持续高位运行某些组件甚至需要更长时间才会完成收缩。我见过有同行调整完立即拿组件大小去对比发现没变化就以为命令没生效反复执行好几次其实完全没有必要。4.3 调整后的基本验证重启完成后我做的第一件事是确认两个节点的参数是否一致SELECT INST_ID, NAME, VALUE FROM GV$PARAMETER WHERE NAME IN (sga_max_size,sga_target);然后查看实例状态srvctl status database -d racdb预期看到两个节点都是Online状态监听正常实例都成功启动。再登录到SQLPlus里看动态SGA汇总SHOW SGA从输出里可以确认Total System Global Area已经是20G各组件也按ASMM的初步分配出来了。5. 实战中的坑这些坑我基本都踩过5.1 忘带SID*造成的双节点参数分叉这个坑是RAC操作里最经典的几乎每个RAC DBA都遇到过。RAC环境下如果ALTER SYSTEM不带SID*有些参数会被当作单实例参数处理修改结果只反映在当前实例上。等到另一个节点使用共享SPFILE重启时两个节点的参数值就出现了分叉。我见过最典型的情况是在节点1上执行ALTER SYSTEM SET SGA_TARGET20G没加SID*节点1立即生效了节点2还是老配置。过了几天节点2因为维护重启结果SGA一下子回到了12G业务马上就出问题。不要心存侥幸RAC下的ALTER SYSTEM特别是涉及全局参数的内存调整一律加上SID*。这应该成为肌肉记忆。5.2 只调TARGET不调MAX直接报ORA-00821如果把SGA_TARGET往大调但又没先把SGA_MAX_SIZE同步调大Oracle会立刻给你颜色看SQL ALTER SYSTEM SET SGA_TARGET20G SCOPEBOTH SID*; ERROR at line 1: ORA-00821: Specified value of sga_target greater than sga_max_sizeORA-00821的意思就是SGA_TARGET超过了SGA_MAX_SIZE的上限。这个报错的信息很明确解决方式也明确先把SGA_MAX_SIZE改大再改SGA_TARGET。但由于SGA_MAX_SIZE只能SPFILE重启生效所以一旦出现这个报错你基本就锁定了一个需要重启实例的维护窗口。这也是为什么在调整前就要把SGA_MAX_SIZE和SGA_TARGET的联动关系想清楚而不是等到执行的时候才踩一脚。5.3 误设MEMORY_TARGET导致ASMM失效这个问题比较隐蔽但危害很大。11g里如果设置了MEMORY_TARGET启用AMM自动内存管理SGA_TARGET的语义会被改变它不再是一个工作目标值而是变成了SGA的下限值。在这种情况下你调整SGA_TARGET可能不会产生预期效果。比如你把SGA_TARGET从12G调到20G但因为MEMORY_TARGET还控制着更上层的总内存SGA的实际大小可能还是要看AMM的脸色。而且11g RAC下AMM要和/dev/shm的大小匹配共享内存文件系统不够大的时候实例甚至可能崩溃。RAC生产环境我基本都会确认MEMORY_TARGET0彻底关闭AMM改用ASMM。原因很直白RAC的每个实例都需要在操作系统的共享内存里创建SGA段而AMM依赖/dev/shm多实例环境下/dev/shm很容易不够用。相比之下ASMM配合HugePages的方案在11g RAC上成熟稳定得多也更容易排查问题。所以调整SGA前必须先确认MEMORY_TARGET和SGA_TARGET的语义关系别在AMM模式下用ASMM的思路去操作。5.4 HugePages与新SGA不匹配实例起不来这个问题是我这次调整之前特意检查过的但真有不少人在这一步翻车。场景是这样的SGA从12G调到20GHugePages还停留在6144页对应12G。实例重启时Oracle尝试分配20G的SGA共享内存但HugePages池只有12G结果启动直接报错ORA-27102: out of memory Linux-x86_64 Error: 12: Cannot allocate memory这个报错出现的时候大家往往第一反应是系统内存不够了其实多半是HugePages没跟上SGA的新尺寸。解决办法是在调整SGA之前就把HugePages规划好。公式很简单需要的HugePages页数 SGA_MAX_SIZE / Hugepagesize 余量SGA_MAX_SIZE是20GHugepagesize是2MB那么20G/2M10240页。考虑到Oracle还会留一部分内存不放到HugePages里比如Small Granule、Fixed SGA实际需要加上一点余量。稳妥起见我设置了10300页。修改方式是在/etc/sysctl.conf里调整vm.nr_hugepages10300然后执行sysctl -p生效或者干脆下次重启OS时生效。注意这里的先后顺序一定要先确保HugePages池足够大再重启数据库实例。否则就是实例报错——数据库实例进程想着HugePages分配大块内存共享内存文件系统也帮不上忙整个人就僵在那里。5.5 ASMM组件下限值设得不合理最后这个坑是我见过不少生产事故的根源。有些DBA为了保证Shared Pool稳定在ASMM模式下给SHARED_POOL_SIZE设了一个很大的固定值比如4G。但ASMM自动分配时可能只需要2.5G的Shared Pool多出来的1.5G全是从Buffer Cache那边硬挤出来的。结果就是Buffer Cache被压缩Block命中率下降RAC环境里gc buffer busy acquire激增业务反而更慢了。ASMM模式存在的意义就是动态调配你非要给它设一个过高的下限等于是拿Buffer Cache的命去填Shared Pool的坑。合理的做法是设置一个保守的下限而不是理想值。比如确保Shared Pool不低于1G或者结合AWR分析确认它实际长期平均值是多少再往下浮一点设置。设置之后还要持续观察Buffer Cache这边有没有被挤压的迹象。6. 调整之后效果验证与后续盯哪些指标6.1 用V$SGA_DYNAMIC_COMPONENTS观察组件自适应变化重启完成后我每隔一段时间就查一次V$SGA_DYNAMIC_COMPONENTS确认ASMM正在按新总量重新分配SELECT COMPONENT, CURRENT_SIZE/1024/1024 AS CURRENT_MB, USER_SPECIFIED_SIZE/1024/1024 AS USER_SPECIFIED_MB FROM V$SGA_DYNAMIC_COMPONENTS ORDER BY CURRENT_SIZE DESC;实际观察中Buffer Cache会逐渐吃下大部分新增的8G空间Shared Pool也会有一定增长Large Pool稍微增加。这就是ASMM正常工作的表现谁需要得多谁拿得多。如果发现某个组件长时间没有变化可以考虑用V$SGA_RESIZE_OPS去看历史上数据库做了哪些resize操作SELECT COMPONENT, OPER_TYPE, OPER_MODE, GRANULE_SIZE, START_TIME FROM V$SGA_RESIZE_OPS ORDER BY START_TIME DESC;V$SGA_RESIZE_OPS这个视图记录了SGA组件的resize历史可以验证在调整后什么时间点发生了哪些内存移动对于判断ASMM是否按照预期工作很有帮助。6.2 AWR报告前后关键指标对比调整后一周左右我重新拉了一份AWR报告和调整前做对比重点看四个指标Buffer Cache Hit Ratio是否回到99%以上ORA-04031的报错是否完全消失gc buffer busy acquire和buffer busy wait是否从Top 5掉出去DB Time和DB CPU的比值是否恢复正常最终的数据比较理想Buffer Cache Hit Ratio回升到99.4%ORA-04031没有再出现gc buffer busy acquire的等待时间占比明显下降。业务方反馈高峰期的卡顿感也消失了。6.3 长期监控建议SGA调整不是一劳永逸的后续还是需要持续关注Alert Log里是否有内存相关的告警。我在这次调整后做了两件长期监控的事。第一在监控系统里增加了对ORA-04031的日志关键字告警出现一次就能第一时间收到通知。第二把SGA组件视图的采集周期缩短了每天自动采集一次v$sga_dynamic_components和v$sga_resize_ops这样即使ASMM后续做了大规模resize也能从历史数据里追溯原因。另外一条经验是如果业务增长频繁SGA_MAX_SIZE这个天花板建议尽量留足未来一到两年的余量。这次从12G调到20G其实MAX直接可以设置到24G甚至更高以后业务再涨只需要动态调整SGA_TARGET完全不需要重启。提前把MAX设大一点等于为未来的扩容留了一条快车道成本只是多占一点地址空间并不会真的多占物理内存。这次调整SGA的过程说到底就是三件事把背景看清、把参数联动关系理清、把外围配置HugePages等同步到位。真正执行ALTER SYSTEM的时间不超过十分钟剩余的时间全花在检查和验证上。把这些检查步骤沉淀成固定的checklist下次再遇到类似的SGA调整需求基本就不会出什么幺蛾子了。