ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ORA-04031共享池分配失败:根因分析与DBA实战调优

ORA-04031共享池分配失败:根因分析与DBA实战调优 又接到电话了应用侧报来一条错误看截图就是经典的ORA-04031: 无法分配 4104 字节的共享内存 (shared pool,...)。老 DBA 看到这串错误第一反应基本一致共享池出事了。这个错误在 Oracle 10g/11g 时代特别常见尤其是那种手动管理 SGA 的库赶上月末跑批、大量硬解析、应用没用绑定变量很容易踩中。虽然现在 12c/19c 用自动内存管理的实例少了很多但存量系统里手改参数的库不在少数这故障依然是 DBA 值班日志里的常客。这篇文章我不打算把概念讲得特别“教科书”就从实际排查的角度聊聊ORA-04031 到底是什么机制、常见原因有哪些、怎么一步步定位、参数怎么调最后用一个我处理过的真实案例收尾。内容更适合正在接手 Oracle 运维、想系统梳理这个报错排查思路的同行参考。新手看这篇也不会太吃力涉及的概念我会尽量说白。1. ORA-04031 到底在说什么1.1 错误产生的底层机制ORA-04031 的完整英文提示通常是这样的ORA-04031: unable to allocate 4104 bytes of shared memory (shared pool,...)中文环境下显示为“无法分配 xxx 字节的共享内存”。它发生在 Oracle 尝试从共享池Shared Pool分配一段连续内存时找不到足够大的空闲块。注意“连续”这两个字这是关键。共享池不是一整块大一统的堆它内部按功能划分成多个子池Subpool每个子池里又被拆成大量大小不一的 ChunkOracle 用一套类似 malloc/free 的机制管理。请求 4104 字节如果当前没有任何一个空闲 Chunk 能装下它就会报 ORA-04031。很多人会把 ORA-04031 和 ORA-4030、ORA-4031 搞混这里简单区分一下ORA-04030进程无法分配内存PGA 相关ORA-04031共享池无法分配共享内存SGA 相关ORA-04033共享池内存不足的另一种表现通常伴随大量保留区碎片ORA-04031 出现的本质无非三种情况要么总内存确实不够要么空闲内存被碎片化到没有足够大的连续块要么某个组件突然申请了一大块内存打破了平衡。搞懂这一点后面排查思路就清晰了先看总量够不够再看碎片化严不严重最后看谁在抢内存。1.2 共享池里到底住着谁想排查 ORA-04031得先知道共享池里都存了些什么。共享池主要包含这几大块Library Cache库缓存存放 SQL 和 PL/SQL 的解析结果、执行计划。这是共享池里消耗最大的部分硬解析越多占的地方越大。Data Dictionary Cache字典缓存存放表、索引、用户、权限等元数据信息。DDL 操作频繁时会大量占用。SQL 游标和会话信息在共享服务器MTS模式下UGA 可能会被分配到共享池里这块很容易被忽略。Reserved Pool保留池SHARED_POOL_RESERVED_SIZE 指定的区域用来应对大块内存申请比如加载大的 PL/SQL 包。其他控制结构比如队列资源、锁管理结构、并行执行相关的消息缓冲区等。日常运维中最常见的场景就是 Library Cache 把共享池塞满或者挤占完之后碎片化加剧导致剩下的 free memory 虽然有总量却没有能匹配请求的连续块。2. 为什么好好的库突然就报错了ORA-04031 出现之前其实数据库已经“忍了很久”。它不是瞬时崩溃而是内存水位一点点恶化后的最终表现。我总结了几类高发诱因大家可以对着现象自查。2.1 参数配置失当手动管理 SGA 的老库最危险如果实例使用的是手动 SGA 管理没有设置 SGA_TARGET 或 MEMORY_TARGET那么 SHARED_POOL_SIZE 是固定值Oracle 不会根据负载自动扩展。很多老库上线时 DBA 把共享池设成 300MB、500MB当时业务量小够用三五年后业务翻了十几倍共享池还停留在原来的水位。还有一类情况是环境里设置了 SGA_TARGET但 SHARED_POOL_SIZE 也显式给了非零值。按 Oracle 的规则SGA_TARGET 启用后SHARED_POOL_SIZE 会被当成“最小值”自动管理策略会在这个值之上动态调整。如果 SGA_TARGET 整体给得不够扩展空间有限共享池还是会卡在某个狭小的范围内动弹不得。2.2 应用侧不带绑定变量硬解析把 Library Cache 撑爆这是最常见的业务侧原因。比如一段 Java 代码里写String sql SELECT * FROM t_order WHERE order_id orderId;每条不同的 orderId 都会生成一个独立的 SQL 文本Oracle 就必须做一次硬解析在 Library Cache 里建立独立的游标对象。一个每秒几千笔交易的系统跑不了一会儿就能生成几万个甚至几十万个子游标。Library Cache 的占用会像雪球一样滚大最后把 free memory 挤到个位数 MB甚至完全清零。判断这类问题的 SQL 很简单查 LOADS 排名靠前的 SQLSELECT sql_id, sql_text, loads, executions, ROUND(executions / DECODE(loads, 0, 1, loads), 1) AS exec_per_load FROM v$sql WHERE loads 100 ORDER BY loads DESC FETCH FIRST 20 ROWS ONLY;exec_per_load理想情况下应该是一个 SQL 只加载一次执行成千上万次。如果这个比值接近 1说明这个 SQL 每次都在重新解析那基本可以断定应用没有绑定变量。2.3 大量匿名 PL/SQL 或大对象反复加载还有一种经典场景应用里频繁调用匿名 PL/SQL 块、动态拼接的存储过程或者每隔几秒就加载一遍很大的 Package。Oracle 加载大对象的时候需要在共享池里找一段足够大的连续内存来放。如果此时的 free chunk 被拆得七零八落哪怕总量还剩几百 MB也找不到一个能装下这个大对象的连续区域。这类报错的信息很有特征错误信息里会明确写出是哪个对象比如ORA-04031: 无法分配 50000 字节的共享内存 (shared pool,package body SCOTT.PKG_REPORT)看到package body出现基本可以锁定是大对象加载触发的问题。2.4 共享服务器模式下的 UGA 挤占共享池如果数据库配置了共享服务器Shared Server模式并且没有设置合适的 LARGE_POOL_SIZE会话的 UGAUser Global Area会被放到共享池里。当并发共享会话很多时UGA 会大量消耗共享池内存。这种情况的排查重点不是 Library Cache而是查询会话的 UGA 占用。检查 SQLSELECT SUM(value) / 1024 / 1024 AS uga_mb FROM v$sesstat WHERE statistic_name session uga memory;如果这个值接近甚至超过共享池的剩余空间问题基本就出在这里。3. 故障排查的全流程实操收到 ORA-04031 的报错后不建议立刻去动参数或者重启库。先做好下面几步把现场信息固定下来后面处理才有的放矢。3.1 第一步从 Alert 日志拿到第一现场所有 ORA-04031 报错都会完整记录在告警日志里路径一般是$ORACLE_BASE/diag/rdbms/dbname/sid/trace/alert_sid.log。进去之后直接搜索最近的ORA-04031关键字重点看两样东西报错的具体时间点和业务反馈的时间对上报错信息里提到的对象名称是普通的 SQL 文本还是 package body还是其他共享池组件举个例子ORA-04031: 无法分配 4104 字节的共享内存 (shared pool,select id,name from user_info where ... ,SQLAREAMEM,cursor)后面的SQLAREAMEM ... cursor说明是创建游标时分配失败指向的是硬解析导致的 Library Cache 空间不足。如果是package body则指向大对象加载失败。这一行信息能直接帮你定调排查方向。3.2 第二步检查共享池余粮连接数据库如果还能登得进去的话首先查整体 SGA 和共享池的使用情况-- 查看共享池内各个组件的占用情况 SELECT pool, name, ROUND(bytes/1024/1024, 2) AS size_mb FROM v$sgastat WHERE pool shared pool ORDER BY bytes DESC;重点关注free memory这一项的数值。如果 free memory 已经小于报错请求的字节数甚至直接是 0说明共享池确实被吃干了。要是 free memory 总量还有几十上百 MB但依旧报无法分配那就说明问题出在碎片化内存虽然多但没有连续块。接着看保留池的状态SELECT request_misses, request_failures, last_failure_size, free_space FROM v$shared_pool_reserved;request_failures表示保留池中分配失败的次数last_failure_size是最近一次失败时请求的字节大小。这个字段很有价值能直接告诉你是多大的请求失败了。3.3 第三步定位内存被谁吃了确认总内存确实吃紧之后需要找出最占共享池的对象。执行下面几条 SQL分别从不同维度看-- 共享池里占用最大的 10 个对象 SELECT * FROM ( SELECT SUBSTR(NAME, 1, 60) AS object_name, TYPE, SHARABLE_MEM FROM v$db_object_cache ORDER BY SHARABLE_MEM DESC ) WHERE ROWNUM 10; -- 按子池维度看 free memory 分布辅助判断碎片化 SELECT KSMCHIDX, KSMCHCLS, COUNT(*) AS chunk_count, SUM(KSMCHSIZ) AS total_bytes FROM x$ksmsp GROUP BY KSMCHIDX, KSMCHCLS ORDER BY KSMCHIDX, KSMCHCLS;x$ksmsp是共享池内存块的底层视图KSMCHIDX是子池编号KSMCHCLS是 Chunk 的类型。如果看到某个子池里的free类型的 Chunk 数量特别多但单块大小都很小比如一两千字节这就是典型的碎片化特征。不同子池之间内存分配不均的情况也很常见——某个子池已经被耗尽了另一个子池却还有余量Oracle 在这种情况下是无法把“别的子池的空闲块”拿来应急的。3.4 第四步反向验证原因根据上面几步拿到的信息回头结合业务场景判断根因现象大概率原因验证方法free memory 接近 0v$sql 中大量 LOADS 高硬解析过多查 v$sql 看 exec_per_load 比值free memory 有剩余但 request_failures 不为 0共享池碎片化查 x$ksmsp 看 free chunk 分布报错对象是 package body大对象反复加载查 v$db_object_cache 找大对象共享服务器模式session uga memory 过大UGA 挤占对比 v$sesstat 的 UGA 占用报错集中发生在跑批时段并发大事务或大查询加剧消耗结合时间点查 ASH/AWR这套流程走完大部分场景的根因就清楚了。接下来才是处理环节。4. 解决方案与参数调优实操ORA-04031 的处理不只有“加内存”一条路。根据不同的根因措施组合也不一样。下面按从“应急止血”到“根源治理”的顺序讲。4.1 应急方案先让业务恢复如果报错频率很高、业务已经受损最直接的做法是重启实例。重启后共享池清空能撑一段时间。但请注意这属于“用时间换空间”的缓兵之计如果根因没解决过不了多久错误会再次出现而且多半会以更猛烈的姿态回来。比重启更温和的应急操作是ALTER SYSTEM FLUSH SHARED_POOL;这个命令会把库缓存和字典缓存全部清掉让共享池短暂回到比较空的状态。但是务必要清楚副作用flush 之后所有 SQL 都要重新硬解析如果是高峰期执行反而会在短时间内制造一大批硬解析可能把共享池瞬间再次塞满。所以我个人强烈建议flush 操作尽量选在低谷期或者确认当前系统的硬解析压力不大的时候用。高峰期宁可重启应用分批重试也不要贸然 flush。4.2 参数调整给共享池科学扩内存确认总量不足后调整参数是绕不开的。在 11g 及以上版本如果我接手一台手动管理 SGA 的老库最推荐的改法是启用自动共享内存管理ALTER SYSTEM SET SGA_TARGET 8G SCOPE BOTH; ALTER SYSTEM SET SHARED_POOL_SIZE 0 SCOPE BOTH;把SHARED_POOL_SIZE置 0Oracle 就会根据负载自动调整共享池大小这个方案能有效解决静态配置跟不上业务发展的问题。注意一点如果库里开了 AMMMEMORY_TARGET那更省事直接把MEMORY_TARGET调大就行SGA 和 PGA 全自动分配。但如果某些特殊原因必须保持手动管理那只能手动调大 SHARED_POOL_SIZE。调多少合适我一般这么估算先看当前共享池的 Library Cache 实际占用和历史 AWR 里Shared Pool Statistics的建议值通常取“当前峰值占用 20%~30% 余量”作为新大小。举个例子v$sgastat 里 shared pool 总占用已经到 1.6GB那我就设 2GB 起步给足发展空间。另外一个常被忽略的参数是SHARED_POOL_RESERVED_SIZE。它的默认值是 SHARED_POOL_SIZE 的 5%用于应对大对象加载请求。如果报错场景是老出现大 Package 加载失败建议手动调大保留区到 10%~15%ALTER SYSTEM SET SHARED_POOL_RESERVED_SIZE 300M SCOPE BOTH;调整时注意别把保留区设得太夸张因为保留区是共享池内部划出来的一块设太大反而压缩了普通内存区的可用空间。上限建议不超过共享池的 25%。4.3 应用侧治理绑定变量是长久之计参数调完只是治标真正要根治还是得看应用。绑定变量是最核心的手段没有之一。把 SQL 改成String sql SELECT * FROM t_order WHERE order_id ?; PreparedStatement ps conn.prepareStatement(sql); ps.setLong(1, orderId);改写后同一模式的 SQL 共享同一个游标Library Cache 压力会呈数量级下降。很多系统在完成绑定变量改造后共享池占用能下降一半以上。如果应用代码一时半会儿改不动Oracle 也留了一个临时方案CURSOR_SHARING FORCE。这个参数的作用是把 SQL 里的字面量自动替换成绑定变量让相近 SQL 共享游标。设置方式ALTER SYSTEM SET CURSOR_SHARING FORCE SCOPE BOTH;但我必须泼一盆冷水CURSOR_SHARINGFORCE只是一个过渡手段它把硬解析压力转嫁成了 CPU 开销和子游标管理开销而且可能引发执行计划不稳定。用它续命可以但别指望它是终极解。终极方案永远是把代码改干净。4.4 大对象加载问题的专项优化如果是大型 PL/SQL 包反复加载导致的碎片化除了调整保留区大小还有一个更精准的工具DBMS_SHARED_POOL.KEEP。它的作用是把指定对象长期固定在共享池里防止被挤出内存。这样每次调用就不用重新加载了。使用前需要先创建这个包默认可能没有$ORACLE_HOME/rdbms/admin/dbmspool.sql然后把关键的大对象 keep 住EXEC DBMS_SHARED_POOL.KEEP(SCOTT.PKG_REPORT);注意这个参数不是把所有大对象一股脑全都 keep。内存有限keep 的对象要精挑细选原则是“体质大 调用频繁 加载耗时长”。keep 太多反而会挤占其他对象空间得不偿失。4.5 其他容易被忽略的调整项如果上面这些都做完了错误还是偶尔出现可以再检查这几个角度SESSION_CACHED_CURSORS适当调大比如从默认的 50 调到 300减少会话反复解析相同 SQL 的次数。OPEN_CURSORS如果应用打开游标数量超额也会间接加剧共享池消耗值设置过低时游标会被频繁关闭再打开。大池 LARGE_POOL_SIZE共享服务器模式下把 UGA 明确挪到大池里共享池只负责真正的缓存工作。隐藏参数 _SHARED_POOL_RESERVED_MIN_ALLOC这个隐藏参数控制进入保留区的最小分配请求大小默认是 4104 字节。像遇到“总是请求 4104 字节但失败”的场景可以把这个值调低让更多小请求也能进入保留区。不过隐藏参数风险高改之前一定要看 MOS 文档确认版本兼容性。5. 一次真实故障的完整复盘理论说了这么多我挑一个印象很深的案例按时间线完整还原一下排查过程。5.1 故障现场那是某个月的月末最后一天晚上 8 点多应用侧开始集中报错全是 ORA-04031。数据库是 11.2.0.4RAC 双节点SGA 手动管理SHARED_POOL_SIZE 配的是 1.5GB。业务是做财务月结正在跑一批汇总报表存储过程。报错信息里带着一条长 SQL是某张日交易流水表的查询语句请求的字节数只有 4104非常小但就是分不出来。5.2 排查过程我先看了 alert 日志确认两个节点都在报但节点 1 更严重差不多每几分钟就报一次。接着连上节点 1 查 v$sgastat结果 shared pool 里free memory只有 800KBLibrary Cache 占掉了近 1.2GB这个分配比例显然不正常。再看 v$sql按 loads 排序后发现了一件非常典型的事一个查询模式SQL 文本结构完全一样只是where条件里的日期和金额值是数字结果出现了 4000 多个不同的 sql_id。每个 sql_id 的 LOADS 都是 1EXECUTIONS 也是 1。这就是教科书级的“没有绑定变量”每个不同的值都硬解析出了独立游标把 Library Cache 彻底吃满了。顺手查了 x$ksmspfree chunk 断成了几万个小块最大的 free 块不到 1KB。所以就算我把总量加一些碎片化也会很快再次堵死分配路径。5.3 处理动作应急阶段我先给节点 1 执行了ALTER SYSTEM FLUSH SHARED_POOL等到凌晨业务低谷时重启了实例。随后做了三个核心调整把SHARED_POOL_SIZE从 1.5GB 加到 3GB同时把SHARED_POOL_RESERVED_SIZE从默认 5% 调到 300MB。临时把CURSOR_SHARING设为 FORCE顶住当晚的报表压力。记录下问题 SQL 的完整列表第二天拉上开发团队把财务报表模块里的动态拼接 SQL 全部改成绑定变量写法。第三天凌晨业务低谷收回CURSOR_SHARING改回 EXACT。之后观察了整整一个月共享池 free memory 稳定在 500MB 以上v$sql 里相同模式的游标数降到了两位数。这个故障从根上解决了。5.4 复盘总结这个案例最典型的点就是“小请求 大碎片”。请求只有 4104 字节说明内存总缺口未必很大但碎片化已经让共享池失去了分配能力。如果只看总量1.5GB 的共享池好像还算充裕可实际能用的连续块已经没了。所以遇到这种报错不要只看数字大小要追到 SQL 层面和应用代码层面去找真正的推手。6. 常见问题速查表与避坑经验6.1 快速定位表故障表现第一排查点直接处理手段报错频繁free memory 归零v$sql 查硬解析 SQL绑定变量改造 临时调大共享池报错信息出现 package bodyv$db_object_cache 查大对象调大保留区 KEEP 关键对象报错集中在跑批时段ASH/AWR 看该时段 TOP SQL分流跑批任务优化 TOP SQL共享服务器模式下高频报错v$sesstat 查 UGA 占用调大 LARGE_POOL_SIZE改过参数后仍复发检查是否手动管理 SGA切换 ASMM设 SGA_TARGET所有节点同时报错全局性内存参数问题统一调整参数后滚动重启6.2 避坑经验总结这些年踩过的坑有几个特别想提醒同行不要在业务高峰期 flush shared pool。这个操作看着像是万能灵药实际可能火上浇油最好结合 AWR 低谷窗口执行。不要无脑把 SHARED_POOL_SIZE 调到很大。共享池不是越大越好设置过大会挤占 Buffer Cache 和 PGA 的空间反而引发新的性能问题。扩容要有依据基于 AWR 建议值上下浮动。不要忽略 RAC 环境。RAC 各节点的共享池大小可以不同但资源使用和负载通常不均衡。参数调整时要对比所有节点的使用率避免只改一个节点。不要轻易改动隐藏参数。像_SHARED_POOL_RESERVED_MIN_ALLOC这类参数改前务必确认版本支持测试库验证后再上生产。备份好调整前参数。执行ALTER SYSTEM SET前最好先CREATE PFILE FROM SPFILE万一新参数引发其他问题可以快速回滚。6.3 日常预防建议故障排查做得再好不如提前预防。我这边日常会做这几件事每季度检查一次 AWR 里 Shared Pool 相关的几个关键指标重点看 Library Cache 的 Hit Ratio、硬解析次数和共享池空闲空间趋势。同时建立监控脚本v$sgastat 里 shared pool 的 free memory 低于某个阈值比如总量的 10%就告警而不是等 ORA-04031 报出来才反应。应用上线前的 SQL 评审也要把绑定变量列为硬性检查项。我自己在实际操作中还有一个习惯每次处理完 ORA-04031 故障后会把报错时间点前后半小时的 AWR 报告导出来归档连同当时的参数调整记录一起存进故障文档。下次同类问题再出现时直接翻历史记录比对定位速度快很多。Oracle 的内存故障往往不是单一原因形成了自己的案例库排查效率会有质的提升。
返回列表