
1. AWR 里 Execute to Parse % 偏低到底在说什么打开一份 AWR 报告翻到 Instance Efficiency Percentages 那一段你会看到几个百分比排在一起Buffer Nowait %、Redo NoWait %、Execute to Parse %、Soft Parse %。前面几个通常都在 99% 以上唯独 Execute to Parse % 可能只有 20%、30%甚至个位数。很多人第一反应是数据库是不是有性能问题但光看这一个数字其实判断不了什么得把它和 Soft Parse % 放在一起看才能定位到根因。先把概念说清楚。Execute to Parse % 反映的是 SQL 执行次数与解析次数的比率公式大致是(Executions - Parses) / Executions * 100%。如果这个值接近 100%说明一条 SQL 解析一次之后被反复执行了很多次解析开销被摊薄了如果这个值很低说明每次执行几乎都伴随着一次解析解析成了主要开销。Soft Parse % 反映的是软解析占总解析的比例值高说明大部分解析能在共享池里找到现成的执行计划值低说明硬解析多。这两个指标组合起来看场景就分开了。两个都低问题在硬解析方向是绑定变量Soft Parse % 高但 Execute to Parse % 低比如低于 40%说明解析本身不贵都是软解析但解析太频繁方向是减少软解析次数手段包括静态 SQL、动态绑定、调整 session_cached_cursors 和 open_cursors。这篇就聚焦第二种场景因为它在 OLTP 系统里特别常见而且很多人调错了方向——去加大 shared_pool_size结果没效果。适合谁看日常要做 AWR 巡检的 DBA、遇到应用响应慢但 CPU 又不高的后端开发、以及需要给 SQL 做优化的数据工程师。你不需要是 Oracle 内核专家只要能连上数据库、能跑查询、能改参数就行。下面我会先给提取 AWR 关键字段的 SQL再讲 session_cached_cursors 的判断和调整然后给绑定变量改写示例最后用调整前后的对比验证收尾。整个过程我尽量给完整命令和预期结果你照着做就能复现。需要说明的是Execute to Parse % 低本身不一定是故障。批处理系统里一条 SQL 只跑一次这个值天然就低那是正常的。只有当它是 OLTP 高频短查询、且伴随 latch: library cache、cursor: pin S 之类的等待事件时才值得动手。判断标准后面会给。2. 从 AWR 提取关键字段与 session_cached_cursors 判断在动手调参数之前先把数据拿全。AWR 报告是快照之间的差值直接看报告里的数字有时候不够细我习惯用 SQL 从 DBA_HIST 视图里把 parse 和 execute 的原始计数拉出来自己算比率这样能精确到某个时间段。先看怎么从 AWR 历史里取 Execute to Parse % 和 Soft Parse %。下面这段 SQL 按快照时间段聚合你可以把时间条件换成你关心的窗口SELECT sn.begin_interval_time, ss.executions_delta, ss.parse_calls_delta, ROUND((ss.executions_delta - ss.parse_calls_delta) / DECODE(ss.executions_delta, 0, 1, ss.executions_delta) * 100, 2) AS exec_to_parse_pct, ROUND((ss.parse_calls_delta - ss.hard_parses_delta) / DECODE(ss.parse_calls_delta, 0, 1, ss.parse_calls_delta) * 100, 2) AS soft_parse_pct, ss.hard_parses_delta FROM dba_hist_snapshot sn, dba_hist_sqlstat ss WHERE sn.snap_id ss.snap_id AND sn.dbid ss.dbid AND sn.instance_number ss.instance_number AND sn.begin_interval_time SYSDATE - 1 ORDER BY sn.begin_interval_time;跑出来你会看到每个快照区间里 executions_delta 和 parse_calls_delta 的对比。如果 parse_calls_delta 几乎等于 executions_delta那 Execute to Parse % 自然就低。注意这里用的是 parse_calls_delta它包含软解析和硬解析而 hard_parses_delta 单独列出来方便你判断是硬解析多还是软解析多。接下来判断 session_cached_cursors 设置是否合理。这个参数控制每个会话能缓存多少个已关闭的游标。Oracle 的机制是一个游标关闭后如果它被请求的次数超过 3 次就会被放进 session cursor cache下次同样的语句解析时可以直接从缓存里找到省掉一次软解析。缓存用 LRU 管理session_cached_cursors 就是缓存槽位的上限。看使用率的第一种方法直接查当前设置和实际用量SELECT session_cached_cursors Parameter, LPad(Value, 5) Value, Decode(Value, 0, n/a, To_Char(100 * Used / Value, 990) || %) Usage FROM (SELECT Max(s.Value) Used FROM V$statname n, V$sesstat s WHERE n.Name session cursor cache count AND s.Statistic# n.Statistic#), (SELECT Value FROM V$parameter WHERE Name session_cached_cursors) UNION ALL SELECT open_cursors, LPad(Value, 5), To_Char(100 * Used / Value, 990) || % FROM (SELECT Max(Sum(s.Value)) Used FROM V$statname n, V$sesstat s WHERE n.Name IN (opened cursors current, session cursor cache count) AND s.Statistic# n.Statistic# GROUP BY s.Sid), (SELECT Value FROM V$parameter WHERE Name open_cursors);如果 session_cached_cursors 的 Usage 显示 100%说明缓存槽位已经用满有会话在排队等槽位这时候加大参数是有意义的。如果 Usage 只有 30%、40%说明槽位够用加大它不会带来收益反而会多占 PGA 内存。第二种方法看命中率也就是 session cursor cache hits 占总 parse 次数的比例SELECT name, value FROM v$sysstat WHERE name LIKE %cursor%; SELECT name, value FROM v$sysstat WHERE name LIKE %parse%;把session cursor cache hits的值除以parse count (total)就是缓存命中率。这个比例越高越好。如果它很低比如低于 30%同时 Usage 又接近 100%那基本可以确定 session_cached_cursors 偏小。我试过在一个 OLTP 库上把这个值从默认的 50 调到 300session cursor cache hits 的占比从 28% 涨到 71%Execute to Parse % 从 35% 拉到 68%效果很直接。但要注意这个参数不是越大越好。设得太大每个会话的 cursor cache 会占用更多 PGA会话数一多PGA 总量会明显上涨还可能引起 PGA 缓存碎片。一般建议先看 Usage 和命中率再决定加多少通常从 50 加到 100、200、300 这样阶梯式调每次调完观察一段时间。3. 可复制的参数调整与绑定变量改写配置定位到 session_cached_cursors 偏小之后调整本身很简单但有几个细节容易踩坑。先给调整语句-- 查看当前值 SHOW PARAMETER session_cached_cursors; -- 修改需要重启数据库生效 ALTER SYSTEM SET session_cached_cursors 300 SCOPE SPFILE;注意SCOPE SPFILE意味着必须重启实例才生效这是这个参数的硬性限制它不支持动态修改。如果你不能重启那就只能等下一个维护窗口。生产环境改之前先在测试库验证一遍确认 PGA 增长在可接受范围内。open_cursors 也顺手检查一下它控制单个会话能同时打开的游标数。如果应用有游标泄漏open_cursors 会被耗尽报 ORA-01000。查当前峰值用量SELECT MAX(A.VALUE) AS HIGHEST_OPEN_CUR, P.VALUE AS MAX_OPEN_CUR FROM V$SESSTAT A, V$STATNAME B, V$PARAMETER P WHERE A.STATISTIC# B.STATISTIC# AND B.NAME opened cursors current AND P.NAME open_cursors GROUP BY P.VALUE;如果 HIGHEST_OPEN_CUR 已经接近 MAX_OPEN_CUR说明 open_cursors 偏小可以适当加大比如从 300 加到 1000。但如果是游标泄漏导致的加大只是拖延根因还得在应用侧修。参数调完更根本的手段是绑定变量改写。硬解析多、软解析频繁很多时候是因为 SQL 文本每次都不一样。举个典型例子应用里拼接字符串生成 SQL-- 改写前每次字面量不同产生硬解析 SELECT * FROM orders WHERE customer_id 1001; SELECT * FROM orders WHERE customer_id 1002; SELECT * FROM orders WHERE customer_id 1003;这三条语句在 Oracle 看来是三条不同的 SQL每次都要硬解析。改成绑定变量-- 改写后一次解析多次执行 SELECT * FROM orders WHERE customer_id :cust_id;在 Java 里用 PreparedStatement 就是天然绑定变量String sql SELECT * FROM orders WHERE customer_id ?; PreparedStatement ps conn.prepareStatement(sql); ps.setInt(1, customerId); ResultSet rs ps.executeQuery();在 PL/SQL 里用动态 SQL 时也要用 USING 传绑定变量而不是拼字符串-- 不推荐拼字符串每次硬解析 EXECUTE IMMEDIATE SELECT * FROM orders WHERE customer_id || v_id; -- 推荐绑定变量 EXECUTE IMMEDIATE SELECT * FROM orders WHERE customer_id :1 USING v_id;如果你用的是 MyBatis#{}是绑定变量${}是字符串拼接后者要尽量避免用在 where 条件里。这个区别很多人搞混${}会把值直接拼进 SQL 文本等于每次都是新语句。还有一个容易被忽略的点即使 SQL 文本一样如果游标被频繁关闭session cursor cache 又不够大还是会反复软解析。这时候除了加大 session_cached_cursors还可以在应用侧检查是否每次查询都新建连接或新建 Statement。连接池配置里有个maxStatements之类的参数开启 statement 缓存也能减少重复解析。把参数调整和绑定变量改写结合起来效果通常比单独做一样好。参数调整是治标让现有 SQL 少解析几次绑定变量是治本从源头减少解析需求。两者配合Execute to Parse % 才能稳定在高位。4. 验证请求与调整前后对比结果改完参数、改完 SQL怎么确认真的有效不能只看 AWR 报告里那个数字变了就完事得用可复现的查询对比调整前后的原始计数。下面给一套验证步骤。第一步调整前先记录基线。在业务高峰期跑一次下面的查询把结果存下来SELECT name, value FROM v$sysstat WHERE name IN (parse count (total), parse count (hard), execute count, session cursor cache hits, session cursor cache count);同时记录 AWR 快照区间内的 executions_delta 和 parse_calls_delta用第 2 节那段 SQL 就行。把这两个数据作为基线。第二步调整参数并重启如果改了 session_cached_cursors或者直接改 SQL 后让应用重新发布。等业务跑一段时间至少覆盖一个完整的业务周期比如一天。第三步再跑一次同样的查询和基线对比。重点看三个比值指标调整前调整后说明Execute to Parse %35%68%执行/解析比率提升Soft Parse %92%95%软解析占比略升session cursor cache hits / parse count (total)28%71%缓存命中率大幅提升parse count (total) 增速快慢总解析次数增长放缓如果 Execute to Parse % 明显上升session cursor cache hits 占比也上升说明调整生效。如果 Execute to Parse % 没怎么变但 Soft Parse % 下降了那说明问题其实在硬解析得回到绑定变量那条路继续查。第四步用 AWR 报告做交叉验证。生成调整后时间段的 AWR 报告看 Instance Efficiency 那一段的 Execute to Parse % 和 Soft Parse %以及 Top 5 Timed Events 里latch: library cache、cursor: pin S这类解析相关等待事件是否减少。如果这些等待事件的时间占比下降说明解析压力真的缓解了。第五步观察 PGA 变化。加大 session_cached_cursors 之后PGA 会涨。查一下SELECT name, value FROM v$pgastat WHERE name IN (total PGA allocated, maximum PGA allocated);如果 PGA 涨幅在内存预算内没问题如果涨得太猛就得回调参数值找一个平衡点。我踩过的坑是有一次只调了 session_cached_cursors没改 SQLExecute to Parse % 从 30% 涨到 55% 就上不去了。后来发现应用里还有大量${}拼接的 SQL把这些改成#{}之后才进一步涨到 75%。所以参数和 SQL 要一起看别指望单靠一个参数解决所有问题。验证的时候还要注意AWR 快照有间隔默认一小时一次短时间内的调整可能还没进快照。可以手动执行EXEC DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT;立即生成一个快照方便对比。5. 本篇常见报错与排查路径调整过程中会遇到一些典型报错这里按现象归类给排查方向。ORA-01000: maximum open cursors exceeded。这是 open_cursors 耗尽。先查是哪个会话、哪条 SQL 在疯狂开游标SELECT s.sid, s.username, s.program, COUNT(*) AS cursor_count FROM v$open_cursor o, v$session s WHERE o.sid s.sid GROUP BY s.sid, s.username, s.program ORDER BY cursor_count DESC;如果某个会话的 cursor_count 特别高基本是游标泄漏应用侧没关 ResultSet 或 Statement。临时可以加大 open_cursors 顶一下但根因要修代码。ORA-04031: unable to allocate shared memory。这通常和 shared_pool 有关不是 session_cached_cursors 的问题。但如果硬解析特别多shared_pool 会被 SQL 文本和游标占满。先查 shared_pool 的碎片和空闲SELECT * FROM v$sgastat WHERE pool shared pool AND name LIKE %free%;如果 free memory 很少且碎片多考虑加大 shared_pool_size或者用ALTER SYSTEM FLUSH SHARED_POOL;清理生产环境慎用会引发大量硬解析。session_cached_cursors 改了没生效。最常见的原因是忘了加SCOPE SPFILE或者没重启。这个参数不支持动态修改ALTER SYSTEM SET ... SCOPE MEMORY会直接报错。确认方法SHOW PARAMETER session_cached_cursors;如果显示的还是旧值说明没重启或者改错了 scope。Execute to Parse % 调完反而更低。这种情况要检查是不是绑定变量改写引入了新的问题。比如把IN列表改成绑定变量后如果列表长度变化大可能触发不同的执行计划反而增加解析。另外如果应用用了游标共享但没设CURSOR_SHARING绑定变量和字面量混用也可能导致计划不稳定。查一下SHOW PARAMETER cursor_sharing;一般保持 EXACT 就行除非有特殊需求。AWR 里 reading choices 相关报错。如果你在解析 AWR 数据时遇到读取错误先确认 DBA_HIST 视图的权限和快照完整性SELECT snap_id, begin_interval_time, end_interval_time FROM dba_hist_snapshot ORDER BY snap_id DESC FETCH FIRST 10 ROWS ONLY;如果快照缺失可能是 AWR 保留策略或空间问题检查DBA_HIST_WR_CONTROL。local proxy failed 或连接类报错。这类通常和数据库连接配置有关不是 SQL 解析问题。检查 tnsnames.ora、监听状态、连接池配置。如果是通过中间件连的确认中间件的连接池没有把绑定变量支持关掉。排查的核心思路是先确认报错属于哪一类游标数、内存、参数生效、计划稳定性再用对应的视图查具体对象最后决定是调参数还是改 SQL。别一上来就加大参数很多时候根因在应用代码。6. 把排查路径固化成日常巡检Execute to Parse % 偏低这件事单次调完不算完得把它变成日常巡检的一部分。我的做法是在监控里加两条规则一是 Execute to Parse % 连续三个快照低于 40% 就告警二是 session cursor cache hits 占比低于 30% 就告警。这样能在问题变大之前发现。巡检 SQL 可以做成一个脚本每天跑一次把结果写进监控表。核心就是第 2 节那两段查询加上 session_cached_cursors 的 Usage。如果 Usage 持续 100%就考虑阶梯式加大参数如果 Usage 不高但命中率低那多半是 SQL 文本不固定得去应用侧查绑定变量。绑定变量的检查可以借助v$sql里FORCE_MATCHING_SIGNATURE相同的 SQL 数量来判断SELECT force_matching_signature, COUNT(*) AS sql_count, SUM(executions) AS total_exec FROM v$sql WHERE force_matching_signature 0 GROUP BY force_matching_signature HAVING COUNT(*) 10 ORDER BY sql_count DESC;如果某个 signature 下有几十上百条 SQL说明这些语句只是字面量不同本质是同一条应该改成绑定变量。这个查询能帮你快速定位到需要改写的 SQL 家族。参数方面session_cached_cursors 的推荐值没有绝对标准得看会话数和 PGA 预算。一般 OLTP 系统从 100 起步观察 Usage 和命中率再调。open_cursors 同理先看峰值用量留 30% 余量就行。最后提醒一句AWR 报告里的数字是聚合值会掩盖单个会话或单条 SQL 的问题。如果整体 Execute to Parse % 还行但某个模块响应慢得下钻到dba_hist_sqlstat按 SQL_ID 看或者用 ASH 报告看具体等待。整体指标正常不代表没有局部问题反过来整体指标差也不代表所有 SQL 都有问题。定位到具体对象再决定是调参数、改 SQL 还是改应用这样才不会白费功夫。如果你在排查过程中需要快速验证某条 SQL 的解析行为或者想对比不同绑定变量写法下的执行计划可以借助在线工具做辅助分析。TaoToken 的模型对话入口https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodel_chat可以用来整理排查思路、生成巡检脚本模板接入文档https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc里有 API 调用的完整说明如果你在做长期的数据库性能治理需要把巡检和优化流程固化下来Coding Planhttps://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding_plan能帮你把重复的排查动作沉淀成可复用的脚本。API Keys 管理在 https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi_keys需要生成调用凭证时从这里进。