Oracle ORA-04031 故障排查


一、问题现象

用户反馈数据库操作失败,应用日志中出现:

ORA-04031: unable to allocate 4160 bytes of shared memory

同时,DBA 发现 AWR 快照未生成,怀疑数据库内部出现严重内存问题。


二、排查流程总览

  1. 确认错误发生时间与频率
  2. 定位 Alert Log,提取 trace 文件路径
  3. 分析 trace 文件中的 HEAP DUMP
  4. 判断是否因碎片化或保留池策略导致
  5. 制定解决方案

三、详细排查步骤

步骤 1:查看 Alert Log —— 找到错误入口

查询日志路径 , 以 sysdba 身份登录数据库,执行:

-- 推荐方式(11g+)
SELECT value FROM v$diag_info WHERE name = 'Diag Trace';

输出示例:

/u01/app/oracle/diag/rdbms/orcl/orcl/trace

🔍 操作命令:

cd $ORACLE_BASE/diag/rdbms/orcl/orcl/trace
grep -A5 -B5 "ORA-04031" alert_orcl.log

📄 日志片段(模拟真实):

Tue Jan 14 05:03:22 2026
Errors in file /u01/app/oracle/diag/rdbms/orcl/orcl/trace/orcl_ora_28471.trc:
ORA-04031: unable to allocate 4160 bytes of shared memory 
("shared pool","unknown object","sga heap(6,0)","modification ")

Tue Jan 14 05:03:23 2026
Errors in file /u01/app/oracle/diag/rdbms/orcl/orcl/trace/orcl_ora_28472.trc:
ORA-04031: unable to allocate 4160 bytes of shared memory 
("shared pool","unknown object","sga heap(6,0)","modification ")

✅ 关键信息标注:

日志内容 含义 为什么重要
ORA-04031: unable to allocate 4160 bytes 申请 4160 字节失败 确认是小块内存分配失败(<4400)
"shared pool" 错误发生在共享池 排除 PGA、Large Pool 等其他区域
"sga heap(6,0)" 具体子池和 duration 表明是 第6个子池的永久区(duration 0) 出问题
orcl_ora_28471.trc 对应的 trace 文件 下一步分析入口

💡 结论:问题集中在 subpool 6 的 duration 0 区域,且请求大小为 4160 字节。


步骤 2:分析 Trace 文件 —— 深入 HEAP DUMP

🔍 操作命令:

less orcl_ora_28471.trc
# 在 less 中输入:
/HEAP DUMP

📄 日志片段(重点节选 + 注释):

*** 2026-01-14 05:03:22.123
HEAP DUMP heap name="sga heap(6,0)"  addr=0x7f8b00000000
extent sz=0xfe0 alt=32767 het=32767 rec=0 flg=2 opc=2

EXTENT 0 addr=0x7f8b00000000
Chunk        7f8b00000010 sz=     200    free      "               "
Chunk        7f8b000000e0 sz=   10000    perm      "perm           "
Chunk        7f8b000027f0 sz=     160    free      "               "
Chunk        7f8b000028a0 sz=    8192    perm      "perm           "
...(省略大量 perm 块)...

=======================================
Total heap size    = 805306104        <-- 总大小 ≈768 MB
FREE LISTS:
  Free list length=12
  Chunk        7f8b00000010 sz=     200    free
  Chunk        7f8b000027f0 sz=     160    free
  ...(其他 free 块均 < 500 字节)...
Total free space   = 360             <-- 总空闲仅 360 字节!
Largest free chunk = 200             <-- 最大连续空闲块仅 200 字节!

Reserved FREE LISTS:
  Empty                              <-- 保留池未被使用(因为请求 <4400)
=======================================

✅ 逐行问题标注:

日志行 分析 体现的问题
heap name="sga heap(6,0)" 当前分析的是第6子池的 duration 0 区 局部耗尽:其他子池可能有空闲,但无法借用
perm "perm" 占绝大多数 大量永久对象(如 pinned cursor) duration 0 只增不减,长期积累撑爆该区域
Total free space = 360 总空闲极少 内存几乎耗尽
Largest free chunk = 200 最大连续空闲块仅 200 字节 碎片化严重,无法满足 4160 字节请求
Reserved FREE LISTS: Empty 保留池未参与分配 因 **4160 🎯 核心结论:

不是没内存,而是:

  1. 内存集中在 heap(6,0) 且全是 perm
  2. 空闲块太小(最大仅 200);
  3. 保留池因规则限制无法启用。

步骤 3:验证保留池策略(可选)

查看隐藏参数值:

SELECT x.ksppinm NAME, y.ksppstvl VALUE
FROM x$ksppi x, x$ksppcv y
WHERE x.indx = y.indx
  AND x.ksppinm = '_shared_pool_reserved_min_alloc';

输出:

NAME                                  VALUE
_shared_pool_reserved_min_alloc       4400

✅ 证实:只有 ≥4400 字节的请求才能用保留池,4160 被排除在外。


四、根本原因总结

维度 说明
直接原因 Shared Pool 子池 6 的 duration 0 区域碎片化,最大连续空闲块(200B) < 请求大小(4160B)
深层机制 Oracle 的 Duration 分区机制导致内存无法跨子池/跨 duration 共享
策略限制 保留池默认只服务 ≥4400B 的请求,小请求被拒之门外
触发场景 高频硬解析、大量 PL/SQL 编译、未使用绑定变量等导致 Shared Pool 压力剧增

五、解决方案(推荐顺序)

✅ 方案 1:关闭 Duration 机制(首选)

-- 1. 查看当前值(可选)
SELECT x.ksppinm NAME, y.ksppstvl VALUE
FROM x$ksppi x, x$ksppcv y
WHERE x.indx = y.indx
  AND x.ksppinm = '_enable_shared_pool_durations';

-- 2. 修改参数(需重启生效)
ALTER SYSTEM SET "_enable_shared_pool_durations" = FALSE SCOPE=SPFILE;

-- 3. 重启数据库
SHUTDOWN IMMEDIATE;
STARTUP;

📌 效果:所有子池内存统一管理,避免“一个池子爆满,其他池子空着”的问题。

Oracle 11g 中该参数默认为 TRUE

关闭属于常见优化手段,被广泛应用于生产环境

方面 影响
正面 减少 ORA-04031 风险,内存利用率更均衡
潜在风险 极高并发下 library cache latch 争用可能略增(实际影响极小)
兼容性 安全,Oracle 支持,无需补丁

⚠️ 方案 2:扩容 Shared Pool(辅助)

ALTER SYSTEM SET SHARED_POOL_SIZE = 2G SCOPE=BOTH;

❗ 注意:若不解决碎片问题,扩容后仍可能再次报错。


❌ 不推荐:调小 _shared_pool_reserved_min_alloc

-- 危险!可能导致大对象无法分配
ALTER SYSTEM SET "_shared_pool_reserved_min_alloc" = 4000;

🚫 后果:大量小请求涌入保留池,真正的大请求(如 50KB)反而失败。


六、预防建议

  1. 应用层:强制使用绑定变量,减少硬解析
  2. 监控:定期检查 v$sgastatfree memory
  3. 参数固化:新环境部署时即关闭 _enable_shared_pool_durations
  4. AWR 基线:建立正常时期的 Shared Pool 使用基线,便于异常对比

日常监控 SQL

-- 查看 Shared Pool 空闲情况
SELECT pool, name, bytes 
FROM v$sgastat 
WHERE pool = 'shared pool' AND name = 'free memory';

-- 查看子池分布(需有诊断包)
SELECT * FROM v$shared_pool_reserved;

七、附录:关键日志速查表

日志特征 问题指向
sga heap(X,0) Duration 0 区域耗尽(最常见)
Largest free chunk < 4000 碎片化导致小请求失败
Reserved FREE LISTS: Empty 保留池未启用(通常因 <4400)
大量 perm 永久对象堆积,需检查 pinned cursor
参数 默认值 作用 是否可调
_enable_shared_pool_durations TRUE 是否启用 Duration 分区 ✅ 可关
_shared_pool_reserved_min_alloc 4400 使用保留池的最小申请大小(字节) ❌ 不建议改
SHARED_POOL_SIZE 自动/手动 Shared Pool 总大小 ✅ 可调
SHARED_POOL_RESERVED_SIZE ≈10% of shared_pool 保留池大小 ⚠️ 一般不动

总结流程图

graph TD
A[应用报 ORA-04031] --> B{检查 Alert Log}
B --> C[定位 trace 文件]
C --> D[分析 HEAP DUMP]
D --> E{Largest Free Chunk < 请求大小?}
E -- 是 --> F[检查是否因 <4400 无法用保留池]
F --> G[确认 Duration 0 区域是否膨胀]
G --> H[关闭 _enable_shared_pool_durations]
H --> I[重启数据库]
I --> J[验证问题是否解决]