如何通过Oracle 19c AWR定位导致硬解析过高的SQL语句
先说几个核心判断:AWR中Execute to Parse%低于90%时,基本可以断定绑定变量使用不足。但别急着下结论,还需要配合ASH进一步验证,确认是否真的存在latch: shared pool争抢。此外,那些parse_ratio超过0.9的SQL语句,必须仔细排查是缺少绑定变量、字面量频繁变化,还是NLS参数出现了漂移。
直接看AWR里的Execute to Parse%是否跌破90%
判断绑定变量是否充分利用,最直接的方法就是打开AWR报告,查看Execute to Parse %指标。该百分比位于instance efficiency percentages表格中,Oracle已经自动计算,无需手动统计。数值越低,说明解析占比越高,绑定变量使用效果越差。如果连续多份报告都低于90%,基本可以确定是应用层遗漏了绑定变量,而非偶然的性能抖动。

别被名称误导:Execute to Parse % 并非指“执行一次再解析一次”,而是「平均每执行100次,仅解析几次」的倒推值。95%意味着每100次执行只解析5次;80%则意味着每100次执行需要解析20次。这样对比下来,差距显而易见。
重点在于时段对比。例如,将业务高峰时段(上午9:30–10:30)的报告与非高峰时段(凌晨2:00–3:00)进行对比。如果仅高峰时段出现暴跌,说明压力触发了游标失效或子游标膨胀,不一定是代码本身的问题。
从SQL ordered by Parse Calls查找高频解析语句
翻到AWR报告的SQL Statistics → SQL ordered by Parse Calls部分,重点关注前5条记录。真正危险的不是Parse Calls最高的那条,而是那些Parses和Executions都很高的SQL——例如Parse Calls=12000,Executions=15000,比值接近1,这几乎意味着每次执行都在进行硬解析。
快速验证方法如下:
- 复制该
sql_id,查询v$sql:SELECT sql_text, executions, parse_calls FROM v$sql WHERE sql_id = 'xxx' - 如果
executions = 1但parse_calls > 1,说明该语句反复硬解析后被挤出共享池,这是典型的碎片化信号 - 如果
sql_text中包含WHERE id = 123这样的固定值,而非WHERE id = :b1,那就是绑定变量缺失的有力证据
用DBA_HIST_SQLSTAT算parse_ratio排序
AWR本身不存储解析类型标记,但DBA_HIST_SQLSTAT中包含parse_calls和executions字段,可以间接反映游标复用程度。执行以下查询(记得替换快照范围):
SELECT sql_id, sql_text, parse_calls, executions,
ROUND(parse_calls / NULLIF(executions, 0), 2) parse_ratio
FROM dba_hist_sqltext t
JOIN dba_hist_sqlstat s USING (sql_id)
WHERE s.snap_id BETWEEN &begin_snap AND &end_snap
AND s.parse_calls > 50
AND s.executions > 0
ORDER BY parse_ratio DESC, s.parse_calls DESC;
parse_ratio > 0.9的SQL基本上意味着游标未被重用。但需要过滤掉DBA工具类语句(例如SELECT * FROM v$session),这些语句本身就不适合进入库缓存。
常见的干扰项值得注意:
INSERT INTO ... VALUES (:1, :2, to_date(:7, 'yyyymmddhh24miss'))—— 看似使用了绑定变量,但如果:7的字符串长度每次不同(比如'20260612123456' vs '20260612123'),Oracle会认为字面量不等价,强制创建新的子游标UNBOUND_CURSOR = 'Y'在v$sql_shared_cursor中为真,说明Oracle明确拒绝复用——不是你代码写错,而是环境参数(例如NLS_LENGTH_SEMANTICS)发生了漂移
光看AWR不够,必须配合ASH抓取正在抢latch的活体SQL
AWR是历史汇总,ASH才是实时快照。latch: shared pool高,本质是硬解析卡在获取latch上,不是结果,而是正在发生的争抢。用以下语句抓取最近1分钟的活体SQL:
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,否则默认查询全量历史,速度慢且噪声大 - 按
sql_opname分组(如SELECT、INSERT),避免将DML和查询混在一起导致误判 - 获取
sql_id后,立即查询v$sql的loads和executions:如果executions = 1但loads > 5,说明每次值变化都会触发重编译
最容易被忽视的一点是:许多人查到高parse_ratio的SQL就急着去修改代码,却没有确认它是否真的在抢latch: shared pool。可能只是统计信息过期导致优化器反复生成新执行计划,而latch争用来自另一批完全不同的语句。这种情况下,ASH才是唯一能交叉验证的数据来源。
