ORA-04031 把 Shared Pool 撑爆, Library Cache Pin 锁死上千会话

客户的 ERP 系统早上 8 点准时卡死,AWR 显示 Shared Pool 使用率 97%,Library Cache Lock 和 Library Cache Pin 等待事件排前两位,最后报 ORA-04031。这场景太经典了,但经典不代表好治。我那次花了 6 个小时才稳住,因为根因不是简单的"硬解析太多",而是子池碎片化加绑定变量滥用。

4.1 Shared Pool 子池的碎片化陷阱

Oracle 的 Shared Pool 默认分成 7 个子池(subpool),每个子池有独立的 free list 和 latch。这种设计是为了减少并发争用,但也带来一个问题:如果某个子池的 free memory 被切成大量小块(比如 64B、128B),而你需要申请一个 4KB 的 chunk,即使所有子池的总空闲内存还有 500MB,也可能找不到连续的 4KB 块,于是报 ORA-04031。我们那次用 oradebug 转储 heapdump,发现 subpool 3 的 free list 里 90% 的 chunk 小于 256B,都是因为大量短生命周期的游标(cursor)频繁分配释放导致的。

4.2 绑定变量的副作用:CURSOR_SHARING=SIMILAR 是个坑

客户的开发团队为了省事,把 CURSOR_SHARING 设成 SIMILAR,以为这样所有 SQL 都会自动绑定。但实际上 SIMILAR 只对"安全"的谓词做绑定,比如等值谓词 WHERE col = 'ABC' 会绑定,但范围谓词 WHERE col > 'ABC' 不会绑定。更坑的是,SIMILAR 会产生大量"相似但不同"的子游标(child cursor),每个子游标都要在 Library Cache 里占一块内存,而且它们共享父游标的执行计划,但绑定变量 peeking 时可能因为数据分布不同而生成不同计划,导致更多子游标。

4.3 排查与应急

-- 查看子池碎片化情况
SELECT subpool,
       sum(decode(chunk_size, 64, 1, 0)) chunk_64b,
       sum(decode(chunk_size, 128, 1, 0)) chunk_128b,
       sum(decode(chunk_size, 4096, 1, 0)) chunk_4k,
       sum(chunk_size)/1024/1024 free_mb
FROM x$ksmsp
WHERE ksmchcls = 'free'
GROUP BY subpool;
-- 结果:subpool 3 的 free_mb = 800MB,但 chunk_4k = 0,全是 64B/128B 碎片

-- 查看 Library Cache 对象占用
SELECT namespace,
       count(*) obj_count,
       sum(sharable_mem)/1024/1024 mem_mb
FROM v$db_object_cache
GROUP BY namespace
ORDER BY mem_mb DESC;
-- SQL AREA 占了 12GB,其中 80% 是 child cursor

-- 应急:flush shared pool(会短暂挂住所有硬解析,慎用)
ALTER SYSTEM FLUSH SHARED_POOL;

-- 根治:改 CURSOR_SHARING 为 FORCE,同时让开发改代码用绑定变量
ALTER SYSTEM SET CURSOR_SHARING = FORCE SCOPE = BOTH;

那次故障后,我给客户定了两条铁律:第一,CURSOR_SHARING 永远设 FORCE,SIMILAR 和 EXACT 在生产环境是非常危险的;第二,每天早上 9 点前 DBA 要跑一遍 Shared Pool 健康检查脚本,如果子池碎片化率(小于 1KB 的 chunk 占比)超过 60%,就预警。后来这套规矩执行了半年,ORA-04031 再没出现过。


请使用浏览器的分享功能分享到微信等