☰
Oracle内存争夺战:Shared Pool与Buffer Cache的性能博弈
2026/9/26 5:32:13 网站建设 项目流程

1. 一次"数据库假死"事故的完整复盘:从告警到定位花了四个小时

1.1 业务投诉、CPU飙高和第一轮"排除法"

先讲个真实场景。上个月中旬,一个做零售交易系统的朋友半夜打来电话,说他们生产库动不了了。具体现象是:OMS订单系统大面积超时,后台日志里全是ORA-12170,应用服务器的连接池被打满,业务反馈"点一个按钮要等一分钟"。更扎心的是,数据库CPU使用率直接冲到99%,load average拉到80以上,几台应用服务器像被冻住一样。

我接手后没有急着去"杀会话"或者重启实例,而是先做了三件事:查系统负载、查活动会话数、查当前等待事件。第一轮排除法先把明显的问题摘出去——磁盘I/O没有异常排队,ASM磁盘组的吞吐量正常,数据库没有锁死(没有blocking session大面积堆积),归档日志目录也没满。那就说明问题大概率出在Oracle实例内部的内存和并发机制上。

这里插一句,遇到数据库"卡死",DBA的第一反应往往是"重启解决一切",但在生产环境上,尤其是核心交易库,重启意味着所有连接断开、事务中断、回滚和恢复时间不可控。我给自己定的规矩是:先花十五分钟采集证据,再决定动作。这次事故恰恰验证了这条规矩的价值——证据采集完,原因基本就浮出水面了,而且根本不用重启。

1.2 等待事件里藏着答案:内存组件之间的互相挤压

十五分钟后,我从v$session_wait抓到了现场。大量会话挂在两类等待事件上:一类是latch: shared pool和latch: library cache,另一类是free buffer waits和buffer busy waits。前者说明Shared Pool里的库缓存锁闩竞争极其激烈,后者说明Buffer Cache里的空闲缓冲区供应不上,数据库后台进程DBWR写脏块的速度赶不上前台进程加载数据块的需求。

单独看任何一个等待事件都不会觉得致命,但把两个放在一起,再叠加一个现象就有意思了:这个库的SGA_TARGET是64G,原则上Shared Pool和Buffer Cache可以自己"商量"地盘,可实际查v$sgastat,发现Shared Pool已经被压缩到只剩12G,而Buffer Cache占到了44G,剩下的被Fixed SGA、Redo Log Buffer等吃掉了。

这不是巧合。这个系统跑批期间有一堆没有绑定变量的动态拼接SQL,硬解析暴增,Shared Pool被猛灌;与此同时,跑批程序还在全表扫大表,Buffer Cache不停加载新块,把原本的热点数据全部冲掉。两边都在向SGA要空间,而总预算就那么多,自动调优机制顾此失彼,数据库就像两个人抢一张凳子,谁都没坐稳——CPU全部消耗在循环获取latch、释放latch、再获取latch的自旋上,业务SQL自然跑不动。

这次故障就是典型的Shared Pool与Buffer Cache内存争夺战。下面我把这两个组件到底在干什么、为什么它们最容易被"抢地盘"讲清楚。

2. Shared Pool与Buffer Cache的内存角色:为什么它们最容易被"抢地盘"

2.1 Shared Pool:SQL蓝图和字典信息的"高频交易柜台"

Shared Pool最容易被理解错,很多人以为它就是个SQL缓存。实际上它分成几个职能区域,核心是Library Cache(库缓存)和Row Cache(数据字典缓存)。

Library Cache负责缓存SQL的解析结果,也就是我们说的执行计划。一条SQL进来,Oracle先算哈希值,再在Library Cache里找有没有现成游标,找到就软解析,直接复用;找不到就硬解析,做语法检查、权限检查、生成执行计划,这个过程非常消耗CPU。你想象一下银行柜台:软解析就像老客户直接刷卡走人,硬解析就像新客户要填单子、核身份、签协议,每笔都慢。如果涌进来一万个"新客户",柜台自然瘫痪。

Row Cache则缓存数据字典信息,比如表定义、列定义、用户权限、索引信息。任何SQL执行都要查字典,哪怕走软解析,也需要访问Row Cache。很多DBA只盯着Library Cache的命中率,忽略了Row Cache,其实Row Cache频繁失效同样会加剧硬解析,进而把Shared Pool撑爆。

Shared Pool的容量管理也有讲究。Oracle在Shared Pool里专门划了一块保留池(Reserved Pool),默认大约是Shared Pool的5%,用来满足大块内存请求。当普通列表无法提供连续空闲空间,而保留池也掉链子的时候,Oracle会开始老化旧的SQL游标,强制释放空间。这个过程叫"共享池收缩循环",表现在指标上就是reloads不断攀升、invalidations上升、硬解析比例暴涨。很多DBA发现Shared Pool命中率跌到90%以下时才开始着急,实际上早就有大批会话堵在latch: shared pool上了。

2.2 Buffer Cache:数据块的"中转仓库"也有LRU规则

Buffer Cache对应的是Oracle的Block Buffer,它缓存的是数据文件中的数据块。所有逻辑读都先从Buffer Cache找,找不到才发物理读请求去磁盘搬数据。这个设计是Oracle最核心的优化手段——内存访问速度是磁盘的几十万倍,Buffer Cache命中率越高,数据库整体IO负载就越低。

它的淘汰机制是改良版LRU(Least Recently Used)加上Touch Count计数器。每个缓冲区都有一个年龄(age),LRU链表的冷端会不断被扫描,当内存不够、需要为新读入的数据块腾位置时,Oracle优先淘汰冷端中长时间没有被访问的块。这套机制看起来很公平,但有一个致命软肋:如果一个全表扫描瞬间读入成千上万个块,这些"一次性块"会把LRU链表的热点区域冲刷掉,把原本频繁访问的热点数据挤到冷端,下次访问热点块时反而要从磁盘重新读。

更麻烦的是Buffer Cache写出的异步性。数据块被修改后就变成脏块(Dirty Buffer),需要DBWR后台进程写回磁盘才能变成干净块。脏块在LRU链表上不能被直接覆盖,必须等写出完成。如果DBWR写得太慢,或者脏块比例过高,前台进程就找不到干净缓冲区放置新数据,于是出现free buffer waits。这是一个非常直接的信号:Buffer Cache已经把"仓库货架"堆满了,前台搬运工在等后台清洁工腾位置。

2.3 ASMM/AMM动态调整的滞后性,才是争夺战的总根源

Oracle从9i开始引入SGA_TARGET自动共享内存管理,11g又推出了MEMORY_TARGET自动内存管理,希望让Shared Pool、Buffer Cache、PGA这些组件根据运行状态自动调整大小。原理上很美好:MMON进程定期收集各组件的统计信息,根据内部模型判断哪个组件压力大,然后发指令调整。

但现实世界是残酷的。自动调优不是实时的,它有滞后性,往往需要几分钟甚至更长才能完成一次调整。业务高峰是分分钟变化的,等MMON反应过来,系统可能已经卡死了。而且还有一个更深的坑:在竞争激烈的时候,组件之间会互相"踩踏"。比如Shared Pool因为硬解析暴增而急需扩容,同时Buffer Cache因为大量全表扫描也在申请空间,两个都要涨,SGA_TARGET总预算就那么大,结果是谁都涨不动,反而出现反复收缩、反复扩展的震荡。

更隐蔽的是PGA和SGA之间的争夺。很多DBA开了MEMORY_TARGET后以为万事大吉,但PGA里的排序区、哈希区同样占用总预算。当大量并发排序和大查询PGA膨胀时,SGA整体会被压缩。SGA一压缩,优先牺牲的往往是Shared Pool和Buffer Cache——因为这两个组件都"可以动态调整",而Redo Log Buffer和Fixed SGA是固定的。这就好比一家人压缩开支,先动的是买菜的钱和加油的钱,结果车也没油了,饭也没法做了。

我个人的看法:自动内存管理在小型项目或者开发环境挺好用,对于繁忙的生产库,尤其是负载波动明显的系统,至少要改成SGA_TARGET手动指定最小边界,必要时干脆把关键组件设成最小值(Minimum Size),给自动调优划定安全区间,避免它把某个组件的空间压缩到危险水平。

3. 内存争夺的三条引爆路径:硬解析、Cache污染与PGA膨胀

3.1 路径一:无绑定变量SQL引发的硬解析风暴

这是最经典的一条路径。应用代码里用字符串拼接方式生成SQL,比如:

SELECT * FROM t_order WHERE order_id = '202501150001234'; SELECT * FROM t_order WHERE order_id = '202501150001235'; SELECT * FROM t_order WHERE order_id = '202501150001236';

每一条SQL的文本都不同,哈希计算出来的值必然不同,Oracle只能逐条硬解析。假设一秒钟涌进来2000条这样的SQL,Shared Pool的Library Cache就会被塞满,保留池耗尽,游标不断被挤出、又不断重新解析,latch: shared pool和latch: library cache的争用会吃掉大量CPU,最终所有会话都在抢闩锁,真正执行SQL的会话几乎没有。

硬解析风暴之所以会把Buffer Cache也拖下水,是因为硬解析过程本身要访问数据字典和表统计信息,会产生大量递归SQL和物理读。而每一个新游标执行时,通常又伴随着具体的业务数据访问,Buffer Cache也要跟着加载新的数据块。两边同时加压,内存争夺就此引爆。

判断指纹:系统CPU高,但磁盘I/O不高;v$sysstat中parse count (hard)秒级增量显著;v$librarycache中SQL AREA的reloads大于pins的1%左右。

3.2 路径二:大表全扫描导致Buffer Cache热点数据被冲掉

业务跑批或者报表查询,动不动就select * from t_flow_log where dt >= sysdate - 1,没有合适索引,CBO选择全表扫描。一张大表几千万行,扫描一下就要在Buffer Cache里装载几万个甚至几十万个块。这些块属于"一次型访问",加载完之后大概率不会再被访问,但它们会把原本缓存的热点块挤出LRU链表。

这带来的连锁反应是:普通在线交易本来已经缓存在内存里的订单数据、账户数据被冲掉了,后续访问不得不走物理读,I/O压力骤增;同时由于频繁的物理读,Buffer Cache里需要不断腾位置,free buffer waits飙升。如果业务同时还在大量解析SQL,Shared Pool和Buffer Cache就会同时告急,两边一起陷入恶性循环。

判断指纹:磁盘I/O的db file sequential read和db file scattered read突然升高;v$buffer_pool_statistics中的physical_reads明显增加;AWR的Segments Statistics里出现全表扫描排名靠前的大表。

还有一种更隐蔽的Cache污染方式:超长IN列表。比如程序拼了个五千个值的IN条件,Oracle大概率会选择全表或全索引扫描,然后把这棵大索引的块一晚上全部装进Buffer Cache,污染效果和大表全扫差不多。遇到这种写法,不管数据库多好,内存多大,都扛不住。

3.3 路径三:并发会话膨胀和PGA过度占用

第三个引爆点往往被忽视:连接数暴涨本身就会引发内存争夺。每个会话的PGA里都有一部分内存用于排序、哈希、游标数组。当数据库的连接池失控,比如应用服务器没限制最大连接数,一个实例突然背上几千个会话,PGA总占用会迅速膨胀。

开启了MEMORY_TARGET的库,SGA和PGA共享同一个总内存预算。PGA一膨胀,SGA就被压缩,压缩的又是Shared Pool和Buffer Cache。这时候你去看v$sgastat,会看到两个组件的bytes都在减小,但系统的内存总用量并没降下来——都被PGA吃了。更尴尬的是,PGA膨胀通常伴随大量排序或哈希操作,这些操作本身又会想尽办法利用内存,内存越不够,临时表空间用量越大,磁盘I/O又上来,整个系统三面起火。

判断指纹:v$pgastat中total PGA allocated超过aggregate PGA target;临时表空间使用量持续增长;Shared Pool和Buffer Cache大小同时缩小。

3.4 别忘了底层放大器:latch自旋如何放大家业CPU压力

前面三条路径无论哪条,最终都会演变成latch竞争,而latch竞争是最消耗CPU的。

latch是Oracle内部用于保护内存结构短期一致性的低级锁,等待时间极短,通常以微秒计。Shared Pool上的Library Cache Latch、Buffer Cache上的Cache Buffers LRU Chain Latch,都是高并发访问热点。当两个池子都在承受压力时,大量会话在等待latch而不是在干活。latch等待是自旋式的——进程会反复检查锁是否可用,这会白白消耗CPU时间片,导致CPU从业务处理转向锁等待的空转。

所以你会发现一个奇怪现象:CPU使用率接近100%,但实际吞吐量极低,所有会话的seconds_in_wait不断增长,数据库像"假死"了一样。这个场景我在文章开头的案例里遇到的,也是Oracle内存争夺战最典型的"最终形态"。

4. 五分钟内定位内存争夺:五条SQL和两类报表

4.1 第一眼:当前会话与系统等待事件

现场排查永远是正在进行的会话最直观。执行下面这条SQL,可以快速找到当前的非空闲等待:

select s.sid, s.serial#, s.username, w.event, w.wait_class, w.seconds_in_wait, w.blocking_session from v$session_wait w, v$session s where w.sid = s.sid and w.wait_class <> 'Idle' order by w.seconds_in_wait desc;

如果结果里大量出现latch: shared pool、latch: library cache、free buffer waits、buffer busy waits这四类等待,基本可以判断内存争夺战正在上演。然后再用这条看系统累计等待事件排行:

select * from ( select event, wait_class, total_waits, round(time_waited_micro/1000000, 1) time_sec from v$system_event where wait_class <> 'Idle' order by time_waited_micro desc ) where rownum <= 10;

注意,v$system_event是从实例启动以来的累计值,如果实例已经运行了很长一段时间,光看总数看不出当下趋势。正确做法是连续采两次快照,相减后再排序,才代表最近时段的压力分布。

4.2 第二眼:v$sgastat看内存地盘变化

等待事件说"谁在等",v$sgastat说"内存给了谁"。这是判断"争夺战"的直接证据:

select pool, name, bytes/1024/1024 mb from v$sgastat where pool in ('shared pool', 'buffer cache') order by pool, bytes desc;

正常情况下,Buffer Cache和Shared Pool的大小应该是相对稳定的。如果你发现它们在某一段时间内反复波动,比如Buffer Cache从30G掉到25G,Shared Pool从15G涨到20G,过一阵又换回来,这就说明自动调优正在"反复横跳",两个组件在争抢固定预算。对于生产环境,这种震荡本身就是需要干预的信号。

再把v$sgastat和v$pgastat联合起来看,如果PGA持续高占用,同时Shared Pool被压到min值,说明问题可能从PGA那边来的,调优方向要转向排序区和并发控制,而不是死磕Shared Pool。

4.3 第三眼:命中率统计不是唯一标准

很多DBA迷信命中率。我明确说一下:命中率不是救命指标,它只能作为参考,而且要看趋势。

-- Library Cache命中率 select namespace, pins, reloads, round(100 * (1 - reloads / pins), 2) hit_ratio from v$librarycache where namespace = 'SQL AREA'; -- Buffer Cache命中率 select name, 1 - (physical_reads / (db_block_gets + consistent_gets)) hit_ratio from v$buffer_pool_statistics;

先说Buffer Cache命中率。一个跑批系统全表扫描做得多,命中率可能只有80%,但它不卡;一个OLTP系统命中率99.5%,一旦出现free buffer waits,同样会卡。原因是命中率是平均值,掩盖了瞬时峰值。真正要关注的是命中率的突然下降,以及下降是否伴随了等待事件出现。

Library Cache的reloads/pins比例超过1%就要引起注意,如果还在快速攀升,说明Shared Pool已经不足以支撑当前解析压力,此时看命中率已经晚了,早点转向解析问题分析更重要。

4.4 第四眼:AWR与ASH的正确读法

AWR报告的老套路是看Top 10 Foreground Events、SQL Statistics、Segment Statistics。重点看两块:

第一,Top 10事件里如果同时出现latch: shared pool和free buffer waits,对照Instance Activity Stats里的parse count (hard)是否暴涨,基本就能坐实共享池和缓冲区缓存的争夺。

第二,SQL Statistics for Top SQL里,按Executions排序看是否有大量不同SQL_ID但SQL文本结构极其相似的情况。如果有几十条SQL文本只是条件值不同,那就是绑定变量缺失的铁证。再按Physical Reads排序,看是否有全表扫描的大查询,这部分就是Buffer Cache污染源。

ASH是AWR的补充,用于定位瞬时事件。比如你发现某个时间段内有大量会话集中在latch: shared pool,可以通过dba_hist_active_sess_history把那个时段的top session和top sql拉出来。ASH更适合复盘事故发生后"回放现场"。

select sample_time, session_id, sql_id, event, count(*) from dba_hist_active_sess_history where sample_time between to_timestamp('2025-01-15 21:00:00','YYYY-MM-DD HH24:MI:SS') and to_timestamp('2025-01-15 21:30:00','YYYY-MM-DD HH24:MI:SS') group by sample_time, session_id, sql_id, event order by sample_time, count(*) desc;

如果你没有开启AWR(小库默认只保留7天),至少要让ASH一直开着,这玩意儿在事故排查的时候是真的救命。

5. 止血与根治:从应急调参到应用侧改造的完整路径

5.1 应急止血:调整参数的正确顺序和注意事项

先把最紧急的问题稳住。如果判断是Shared Pool被压爆导致latch竞争,可以手工给Shared Pool划一个最小保证值,同时限制Buffer Cache的最大值,避免自动调优继续震荡。

我建议的操作顺序是:

  1. 先确认SGA_TARGET的大小和当前使用情况。
  2. 设置关键组件的下限(Minimum Size),这一步要谨慎,设置的值不能超过SGA_TARGET减去其他固定组件的剩余空间。
  3. 手工调整shared_pool_size和db_cache_size。
  4. 立刻重跑等待事件查询,观察latch和free buffer是否下降。

以64G SGA_TARGET的库为例,操作可以是这样:

-- 查看当前SGA各组件大小 select component, current_size/1024/1024 mb from v$sga_dynamic_components where component in ('shared pool', 'DEFAULT buffer cache'); -- 给两个核心组件划定底线和上限 alter system set shared_pool_size=16G scope=both; alter system set db_cache_size=28G scope=both;

这里有个坑:在ASMM模式下,db_cache_size设为非零值会被当作该组件的最小值,Oracle仍然可以在SGA_TARGET剩余范围内自动扩展它。不要指望一条命令解决,之后必须持续观察5到10分钟。另外,这些调整最好在业务低谷做,或者至少确认有操作窗口;如果系统已经严重卡顿,一条alter system可能也会堵在latch上,那就只能分拆成scope=memory,或者在维护窗口用spfile重启,但重启永远是我最底牌的方案,会留到最后。

还有一个应急手段:用DBMS_SHARED_POOL.KEEP把已知的高频关键SQL固定住,防止被老化。前提是你知道哪些SQL是关键SQL,需要先通过v$sql查它的SQL_ID。

-- 查找需要固定的SQL select sql_id, executions, loads, parse_calls from v$sql where sql_text like '%t_order%' order by loads desc;

然后固定它:

exec dbms_shared_pool.keep('SQL_ID字符串', 'C');

这种固定对象的做法相当于给SQL加了"免死金牌",可以在一定程度上缓解Shared Pool的蒸发式老化,但治标不治本,放不下所有SQL。

5.2 根治一:绑定变量是最廉价的"内存解药"

所有调Shared Pool参数的手段都只能解决"容量"问题,解决不了"需求"问题。真正要让Shared Pool喘过气来,必须从源头减少硬解析,而绑定变量是最成熟、最便宜的法子。

改前端的修改点一般是把字符串拼SQL改成预编译语句:

// 错误姿势 String sql = "SELECT * FROM t_order WHERE order_id = '" + orderId + "'"; // 正确姿势 String sql = "SELECT * FROM t_order WHERE order_id = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, orderId);

对应到PL/SQL里,就是尽量用静态SQL配合变量,而不是动态拼接再用EXECUTE IMMEDIATE:

-- 正确姿势 SELECT * INTO l_order FROM t_order WHERE order_id = v_order_id; -- 错误姿势 EXECUTE IMMEDIATE 'SELECT * FROM t_order WHERE order_id = ' || v_order_id;

如果短时间改不完代码,可以拿cursor_sharing = force先顶一阵。这个参数会把条件值替换成系统生成的绑定变量,明显降低硬解析数量。但必须提醒的是,它把"所有字面量SQL"都强制绑定,可能会导致执行计划不再针对具体值做优化(bind peeking问题),个别SQL性能反而变差。所以它只适合作为过渡手段,不能作为永久配置。

配套参数还有几个:session_cached_cursors把会话内常用的游标缓存起来,减少游标重复打开关闭;open_cursors确保游标上限够用。通常session_cached_cursors放到200左右能明显降低Library Cache的开销。

5.3 根治二:把内存大户请出Shared Pool

有些东西天生不适合挤在Shared Pool里,硬挤就会加剧争夺。比如RMAN备份通道、并行查询的并子进程、闪回日志等,Oracle专门给了它们一个大池(Large Pool)。如果业务里经常跑大查询并行,或者频繁做RMAN备份,请检查Large Pool是否太小。

我确实见过一个案例,某系统跑并行查询时不开Large Pool,所有并行进程的消息缓冲全在Shared Pool里分配,一跑并行查询,Shared Pool可用空间就被瞬间吃光,然后连带所有普通SQL全部硬解析。查出来的罪魁祸首就是parallel_max_servers设得很大,而Large Pool只有256M。

设置方法很简单:

alter system set large_pool_size=4G scope=both; alter system set parallel_max_servers=16 scope=both;

同时把并行执行的阈值控制住:parallel_min_time_threshold、parallel_degree_limit要配合业务预期设定,别让一个普通查询都开8个并行。

另外,从Oracle 12c开始,如果条件允许,可以把热点小表放到In-Memory列存区。这个需要显式给inmemory_size分配内存,好处是类似全表扫描的查询直接从列存内存返回,根本不进Buffer Cache,也不参与LRU竞争。不过In-Memory对OLTP点查帮助有限,还要占用内存预算,给不给、给多少,要结合实际业务评估,不建议盲目开启。

5.4 根治三:KEEP/RECYCLE池和In-Memory列存储的合理分工

Buffer Cache的"污染"问题,除了靠应用把全表扫描改成索引访问之外,数据库层面也有几个行之有效的隔离手段。

KEEP池(db_keep_cache_size)专门存放你希望长期保留在内存里的高频小表/小索引。它不走默认池的LRU淘汰,只要大小够,块就能长时间驻留。典型的用法是把那些被反复访问的代码表、字典表、小型维度表放进KEEP池。但要注意,KEEP池不是万能的彩票,如果你把一张几十GB的大表硬塞进KEEP池,它只会把整个KEEP池占满,还会造成严重的物理读和缓冲等待,所以只适合放"小而热"的对象。

RECYCLE池(db_recycle_cache_size)则是给那些"用完即弃"的临时大扫描准备的。数据读进来后命中率极低,与其让它们在默认池里污染LRU,不如丢到RECYCLE池,让它们只在池子里转一圈就滚蛋。在19c及以后版本,这个RECYCLE池的迹象越来越少见,因为新版本对LRU辅助列表做了很多优化,但老版本库用得上。

分配方案可以这样设计:

alter system set db_keep_cache_size=4G scope=both; alter system set db_recycle_cache_size=2G scope=both;

然后把小表放进KEEP池:

alter table t_dict_code storage(buffer_pool keep);

更精确的做法是让表所属的表空间设置默认Buffer Pool,避免每次都显式写DDL。设置后通过dba_segments检查buffer_pool列确认是否生效。整体原则是:能放进KEEP池的对象不超过Buffer Cache总大小的10%到20%,否则反而会挤占默认池的额度。

5.5 验证是否回到正常水平的指标

调整完之后,不要立刻宣布"恢复",一定要验证。建议按这个顺序复查:

  1. v$session_wait里latch: shared pool、free buffer waits的会话数快速下降。
  2. v$system_event里两类事件的增量间隔趋于平稳,不再陡增。
  3. v$sgastat里Shared Pool和Buffer Cache大小趋于稳定,不再剧烈震荡。
  4. 业务侧反馈的查询响应时间恢复,应用日志不再刷超时。

通常调完参数后5分钟内等待事件就会明显回落,如果10分钟后仍然卡顿,说明根因不是单纯的内存大小问题,可能要回到SQL层面去挖,或者考虑主机层的内存压力、NUMA亲和等问题。不要在同一招上死磕。

6. 防止重演:一套可持续的内存健康巡检与监控方案

6.1 日常监控的四个关键指标

事后复盘教训是必要的,但更重要的还是预防。我在多个生产环境的监控实践中,保留了四个核心指标,只要这四个指标正常,Shared Pool和Buffer Cache的争夺战基本不会爆发。

第一个是硬解析比例(Hard Parse Ratio),计算公式是parse count (hard) / parse count (total),正常OLTP库里这个值一般控制在10%以内。如果连续几次采样都超过30%,就要怀疑应用层的绑定变量出了问题,或者数据库有变量窥探和游标失效问题。

第二个是Library Cache的reloads/pins比例,超过1%就需要关注。这个指标直接反映Shared Pool的老化压力,比看命中率更灵敏。

第三个是free buffer waits和buffer busy waits的累计增量。连续两次快照间如果这两个事件的秒级增量很大,说明Buffer Cache的腾挪和争用出了问题,需要回头看是否有新上线的大查询,或者KEEP池配置是否被误改。

第四个是SGA组件大小震荡幅度。拿v$sgastat定期记录Shared Pool和Buffer Cache的当前大小,如果发现它们频繁变化,比如每十几分钟改变一次且幅度超过1GB,就说明自动调优在震荡,要么订正系统参数让组件有个稳定下限,要么调整SGA_TARGET总预算。

6.2 每周体检清单

我习惯每周对核心生产库做一次轻量级AWR体检。不需要复杂的审查脚本,重点看几个地方:

  • Top SQL里有没有出现大量SQL文本相似、SQL_ID不同的语句(绑定变量问题)。
  • Segments Statistics里有没有全表扫描排名特别靠前的表。
  • 等待事件Top10里,latch类事件和free buffer waits是否回到正常水平。
  • Instance Activity Stats里parse count (hard)的日均值有没有逐步上升趋势。

如果发现异常,我会直接进v$active_session_history去查最近几天最严重的时段,找具体SQL再做SQL Profile或计划管理。很多故障不是突然爆发的,都是先有不健康的数据在AWR里酝酿了一段时间,然后某一天业务量上来,导火索被点燃。

6.3 我在生产环境坚决不做的几件事

最后分享几条用教训换来的经验,都是我在生产环境踩过坑之后总结出来的。

第一,不在没有监控的情况下直接调小Shared Pool。调整前必须先知道当前计算高峰的硬解析量,也要确认SGA_TARGET里有没有可用的空闲内存。共享池调太小,就是另一种形式的卡死。

第二,不盲目迷信自动调优。ASMM和AMM的本意是减负,但在高并发且负载瞬时波动的系统中,它往往反应慢半拍。我的习惯是给关键组件设置下限,让自动调优只能在安全区间里"伸缩",而不是让它自由飞翔。

第三,不要为了压制Shared Pool竞争就去改隐藏参数。我见过很多DBA遇到共享池不足就翻隐藏参数列表,比如_shared_pool_reserved_min_alloc、_cursor_obsolete_threshold,这些东西在某些场景下确实有效,但它可能导致更难以预料的副作用,而且换了版本可能行为就变了。除非你完全清楚它在做什么,否则不要在生产库乱改。

第四,不要把KEEP池当成万能钥匙。KEEP池是对LRU机制的补充,不是替代。凡是SQL本身有问题、非要全表扫描一个大表的情况,你把表塞进KEEP池也救不了,反而会拖垮内存。SQL层面能优化的,优先在SQL层面解决。

像文章开头的那个零售系统,后来我们做的动作就是:把核心查询改成绑定变量、把跑批的全表扫描SQL改写成分区裁剪、给Shared Pool和Buffer Cache设置了明确的最小值和最大值、同时把连接池上限压到合理范围。折腾完这些以后,再也没出现过业务高峰"卡死"。

内存分配这件事,说到底就是"预算管理"。数据库总内存就那么多,谁能拿到预算、谁拿不到,决定了系统的吞吐和稳定。Shared Pool和Buffer Cache的内战不会消失,但你可以通过合理的参数边界、健康的SQL写法、以及一套有效的监控体系,让它们在预算范围内和平相处。这比什么"一键优化"都靠谱。

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

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

立即咨询