应通过v$active_session_history查最近1分钟event='latch: shared pool'的sql,按sql_id+sql_opname分组聚合,再结合v$sql中loads>executions、unbound_cursor='y'及sql_text含连续单引号等特征确认未绑定变量。
查正在抢 shared pool latch 的活体 sql
别翻 awr 报告里“硬解析总数”,那只是结果;真正要抓的是此刻卡在获取 latch: shared pool 上的语句。用 v$active_session_history 直接采样正在运行的会话,它不依赖 sql 是否缓存成功,哪怕只执行一次、刚硬解析完就被 ora-04031 踢出共享池,也能被捕获。
执行以下语句(注意加时间过滤):
SELECT sql_id, sql_opname, COUNT(*) cnt FROM v$active_session_history WHERE event = 'latch: shared pool' AND sample_time > SYSDATE - 1/1440 GROUP BY sql_id, sql_opname ORDER BY cnt DESC FETCH FIRST 10 ROWS ONLY;
- 必须加
SAMPLE_TIME > SYSDATE - 1/1440(最近 1 分钟),否则默认查全历史,慢且干扰多 - 按
sql_id + sql_opname分组,避免把SELECT和INSERT混在一起,便于定位是查询还是 DML 引发的解析 -
sql_opname值为SELECT/INSERT/UPDATE等,比仅看sql_id更准
验证 SQL 是否真没绑定变量
拿到 top sql_id 后,不能只看执行次数。关键看它是否因字面量不同反复触发硬解析——这类 SQL 在 v$sql 中表现为:低 executions、高 loads、频繁 invalidations,且 sql_text 里含连续单引号(如 WHERE name='JOHN')。
执行验证语句:
SELECT sql_text,
CASE WHEN sql_text LIKE '%''%''%' THEN 'has literal' ELSE 'no literal' END has_literal,
parsing_schema_name, executions, loads, invalidations
FROM v$sql
WHERE sql_id = '&input_sql_id';
- 重点比对
executions和loads:若executions = 1但loads > 5,基本坐实每次换值都重编译 - 查
v$sql_shared_cursor中对应sql_id的UNBOUND_CURSOR列是否为Y——这是 Oracle 明确标记“无法复用游标”的信号 - 别信
CURSOR_SHARING=FORCE:19c 已标记为desupported,它生成的系统绑定变量会导致 ACS 抖动、子游标爆炸,反而拉长 hash chain、加重 latch 争用
为什么调大 shared_pool_size 或 flush shared_pool 是错的
shared pool latch 高不是因为内存不够,而是因为太多字面量 SQL 在高频硬解析。每次硬解析都要分配内存、计算 hash、插入链表,每一步都得抢 latch: shared pool。调大 shared_pool_size 只会让 hash chain 更长、查找更慢;flush shared_pool 更是雪上加霜——刚刷掉旧游标,新字面量 SQL 又涌进来,引发新一轮硬解析风暴。
-
RESULT_CACHE对缓解该问题基本无效:它缓存结果,不减少硬解析;其元数据本身也存于 shared pool,仍受同一 latch 保护,高并发下可能加剧争用 - ORA-04031 错误常伴随出现,但这只是硬解析暴增的副产品,不是根源。杀会话、重启实例只能临时缓解,不解决源头
- 隐含参数如
_library_cache_advice属于极端情况下的临时手段,不可替代应用层绑定变量改造
应用层绑定变量改造的关键落地点
根治必须从应用代码入手。数据库层面没有银弹,只有真实绑定才能让同结构 SQL 复用同一游标,彻底消除重复硬解析。
- JDBC:禁用
implicitCache,显式复用PreparedStatement;避免statement.execute("SELECT * FROM t WHERE id = " + id)这类字符串拼接 - Python(cx_Oracle):必须避免重复调用
cursor.prepare(),应prepare一次、execute多次,参数用:id占位 - .NET(ODP.NET):使用
OracleCommand.Parameters.Add(),而非字符串格式化 - PL/SQL:用
EXECUTE IMMEDIATE ... USING,而非拼接完整 SQL 字符串
最容易被忽略的是那些“看似合理”的动态 SQL 场景——比如根据条件拼接 WHERE 子句,或用不同字段名做排序。这些地方一旦漏掉绑定,就会成为 latch 争用的长期隐患。











