ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

session_cached_cursors过低导致Execute to Parse %过低:TaoToken场景下的游标缓存诊断与调优大纲

session_cached_cursors过低导致Execute to Parse %过低:TaoToken场景下的游标缓存诊断与调优大纲 1. 从一次 AWR 报告说起Execute to Parse % 掉到 30% 以下意味着什么如果你手上有套 Oracle 库最近业务方反馈同样的查询一会儿快一会儿慢你拉了一份 AWR 报告翻到 Instance Efficiency Percentages 那一段看到Execute to Parse %只有 20% 出头那基本可以锁定一个方向硬解析和软解析吃掉了大量 CPU快速软软解析session cursor cache hit没顶上来。先把这几个概念用大白话捋一遍。Oracle 执行一条 SQL大致分三步解析Parse、执行Execute、取数Fetch。解析又分硬解析和软解析。硬解析要做语法语义检查、生成执行计划、去共享池里找地方放开销最大软解析是 SQL 文本已经在共享池里命中省掉了生成执行计划那步但还是要走一遍解析流程而软软解析soft soft parse是最高效的——会话在自己的游标缓存session cursor cache里直接找到了之前用过的游标连库缓存闩锁都不用抢直接跳到执行。Execute to Parse %这个指标衡量的是执行次数相对于解析次数的比例。公式大致是Execute to Parse % (Executions - Parses) / Executions * 100这个值越接近 100%说明解析开销被摊得越薄绝大多数执行都复用了已有游标。反过来如果它偏低说明每次执行背后都跟着一次解析硬解析或软解析占比过高。那session_cached_cursors跟它有什么关系这个参数控制的是每个会话能缓存多少个游标。当一条 SQL 在同一个会话里被反复执行比如 JDBC 连接池里某个连接反复跑同一批语句Oracle 会尝试把这个游标放进会话的私有缓存。下次再执行同样的 SQL如果缓存里还在就直接软软解析跳过库缓存查找。可如果session_cached_cursors设得太小比如默认的 50缓存很快被塞满老游标被挤出去后续 SQL 又得重新走软解析甚至硬解析Execute to Parse %自然就上不去。我见过不少生产库session_cached_cursors还是安装时的默认值 50而实际并发会话几百个、每会话活跃游标上百条缓存使用率早就 100% 了等于这个缓存形同虚设。这篇就按诊断 → 定位 → 调参 → 验证的闭环走一遍所有 SQL 和脚本都能直接复制。顺带说一句如果你在做多模型 API 的统一接入和调用观测TaoToken 那套通道官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 的思路其实和游标缓存有点像——都是把重复的、可复用的东西缓存起来减少每次请求的重复开销只不过一个管数据库游标一个管模型调用。适合谁看日常要盯 Oracle 性能的 DBA、后端开发、运维同学尤其是那些用连接池 预编译语句、但没怎么调过游标缓存的系统。下面进入实操。2. 诊断前置查清 session_cached_cursors 使用率与 Execute to Parse % 现状调参之前先别急着改得先拿到两个证据一是当前session_cached_cursors的值和使用率二是Execute to Parse %到底低到什么程度。这两组数据决定了你到底该不该动这个参数。2.1 查 session_cached_cursors 当前值与使用率先看参数本身show parameter session_cached_cursors;输出大概长这样NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ session_cached_cursors integer 5050 是很多版本的默认值。但光看值没用关键看用满了没有。下面这段 SQL 直接算出使用率百分比是我平时最常用的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);跑出来典型结果PARAMETER VALUE USAGE ---------------------- ---------- ----- session_cached_cursors 50 100% open_cursors 300 20%看到session_cached_cursors那行 USAGE 是 100%就说明缓存已经被塞满有会话想缓存新游标但没位置了。这时候哪怕你把open_cursors调得再大也没用因为瓶颈在会话级缓存不在打开游标总数。注意session cursor cache count统计的是当前所有会话缓存游标数的最大值口径MAX取的是峰值。如果 USAGE 长期贴着 100%基本可以判定缓存不足。2.2 从 AWR 提取 Execute to Parse %session_cached_cursors使用率高只是嫌疑还得看Execute to Parse %是不是真的低。两个途径实时查V$SYSSTAT或者翻 AWR 报告。实时查当前实例累计值SELECT name, value FROM v$sysstat WHERE name IN (execute count, parse count (total), parse count (hard), parse count (failures));拿到execute count和parse count (total)后手算Execute to Parse % (execute count - parse count (total)) / execute count * 100如果这个值低于 70%配合前面缓存 100% 的使用率方向就明确了。AWR 报告里更直观翻到 Instance Efficiency Percentages (Target 100%) 这一段找Execute to Parse %那一行。我手上这份报告里它是 28.6%同时Soft Parse %是 92%说明硬解析不算特别多但软解析一大堆——这正是会话游标缓存没兜住的典型特征SQL 文本能命中共享池所以 Soft Parse 不低但每次还得走软解析没能升级成软软解析。2.3 用 AWR 脚本批量提取关键字段手工翻报告太慢我一般用下面这段 SQL 从DBA_HIST_SYSSTAT里按快照区间拉数据直接算比率WITH snap AS ( SELECT snap_id, begin_interval_time, end_interval_time FROM dba_hist_snapshot WHERE begin_interval_time SYSDATE - 1 ), stat AS ( SELECT s.snap_id, n.name, s.value FROM dba_hist_sysstat s, v$statname n WHERE s.statistic# n.statistic# AND n.name IN (execute count, parse count (total), parse count (hard), session cursor cache hits) ) SELECT a.snap_id, a.end_interval_time, ROUND((e.value - p.value) / NULLIF(e.value, 0) * 100, 2) AS exec_to_parse_pct, ROUND((p.value - h.value) / NULLIF(p.value, 0) * 100, 2) AS soft_parse_pct, (c.value) AS cursor_cache_hits FROM snap a JOIN stat e ON e.snap_id a.snap_id AND e.name execute count JOIN stat p ON p.snap_id a.snap_id AND p.name parse count (total) JOIN stat h ON h.snap_id a.snap_id AND h.name parse count (hard) JOIN stat c ON c.snap_id a.snap_id AND c.name session cursor cache hits ORDER BY a.snap_id;这段脚本能按快照粒度把Execute to Parse %、Soft Parse %和会话游标缓存命中数一起列出来方便你判断是持续偏低还是某个时段突然掉下去。如果session cursor cache hits增长很慢而parse count (total)涨得飞快那缓存不足的结论就更实了。提示session cursor cache hits这个统计项直接反映软软解析的次数它涨得越快说明缓存越有效。调参前后对比这个值的变化比只看Execute to Parse %更灵敏。拿到这两组证据后再决定要不要调session_cached_cursors。如果使用率没满、Execute to Parse %也正常那就别乱动问题可能在别处比如绑定变量没用好、共享池太小。如果使用率 100% 且指标偏低往下走。3. 可复制配置调整 session_cached_cursors 并配套连接池参数确认要调之后动作本身不复杂但有几个坑得提前说清楚这个参数不是动态生效的改完必须重启实例而且它跟连接池的语句缓存配置是配套的只调数据库端、不管应用端效果会打折。3.1 修改参数并重启先确认当前值再用scopespfile改最后重启-- 1. 查看当前值 show parameter session_cached_cursors; -- 2. 修改scopespfile 表示写入参数文件重启后生效 alter system set session_cached_cursors100 scopespfile; -- 3. 重启实例 shutdown immediate; startup; -- 4. 确认新值 show parameter session_cached_cursors;重启后应该看到 VALUE 变成 100。这里有个细节alter system set ... scopespfile执行完show parameter还是显示旧值 50这是正常的因为内存里的值要等重启才刷新。别以为没改成功又去改一遍。那调到多少合适没有万能值。经验做法是先看当前峰值使用量。前面那段使用率 SQL 里的USED就是峰值如果它是 50 且使用率 100%说明至少得往上加。一般可以从 100 起步观察一段时间如果使用率还是接近 100%再往 200、300 加。Oracle 官方文档里提到过这个参数设成 0 会禁用会话游标缓存设得过大比如几千会占用额外 PGA 内存但通常几百的量级对现代服务器来说内存压力可以忽略。我自己的习惯是分两档OLTP 高并发短事务系统直接给到 200报表类、单会话游标多的系统给到 300 甚至 500。改完观察session cursor cache hits的增长曲线如果明显变陡说明加对了。3.2 配套的连接池与语句缓存配置数据库端调好了应用端如果没开语句缓存等于白搭。因为会话游标缓存的前提是同一个会话反复执行同样的 SQL。如果连接池每次拿连接都重新 prepare 语句或者连接被频繁回收重建缓存根本攒不起来。以常见的 HikariCP Oracle JDBC 为例关键配置项# HikariCP 连接池配置 spring.datasource.hikari.maximum-pool-size50 spring.datasource.hikari.minimum-idle10 spring.datasource.hikari.idle-timeout600000 spring.datasource.hikari.max-lifetime1800000 # Oracle JDBC 隐式语句缓存关键 spring.datasource.hikari.data-source-properties.oracle.jdbc.implicitStatementCacheSize50 spring.datasource.hikari.data-source-properties.oracle.jdbc.implicitStatementCacheSize50oracle.jdbc.implicitStatementCacheSize控制的是 JDBC 驱动层面的语句缓存它和数据库的session_cached_cursors是两层缓存驱动层缓存 PreparedStatement 对象数据库层缓存游标。两层都开软软解析才容易命中。如果你用的是 Druidspring.datasource.druid.pool-prepared-statementstrue spring.datasource.druid.max-pool-prepared-statement-per-connection-size50pool-prepared-statementstrue开启池化预编译语句max-pool-prepared-statement-per-connection-size控制每连接缓存的语句数建议和session_cached_cursors保持同一量级或略小。注意连接池的max-lifetime别设太短。如果连接几分钟就被销毁重建会话游标缓存跟着会话一起没了缓存命中率永远上不去。生产环境一般设 30 分钟以上配合数据库端的session_cached_cursors才有意义。3.3 用 JSON 记录调参前后的配置快照为了后面验证对比建议把调参前后的关键配置存成 JSON方便回溯。比如{ before: { session_cached_cursors: 50, open_cursors: 300, implicitStatementCacheSize: 0, exec_to_parse_pct: 28.6, cursor_cache_usage: 100% }, after: { session_cached_cursors: 200, open_cursors: 300, implicitStatementCacheSize: 50, exec_to_parse_pct: null, cursor_cache_usage: null }, change_window: 2025-01-15 02:00 - 02:30, restart_required: true }这份快照在复盘时特别有用尤其是当有人问到底改了什么导致指标变化的时候直接甩出来。配置改完、实例重启完别急着下结论得跑一段真实业务流量再验证。下一节讲怎么验证。4. 验证请求与成功结果对比调参前后的 Execute to Parse % 与缓存命中调参不是改完就完事得用数据证明有效。验证分两步先看会话游标缓存使用率有没有降下来再看Execute to Parse %有没有升上去最后看session cursor cache hits的增长斜率。4.1 重新查使用率重启后跑一遍第 2 节那段使用率 SQLSELECT 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);调参前是50 / 100%调成 200 之后如果业务量没变使用率应该降到 50% 以下比如PARAMETER VALUE USAGE ---------------------- ---------- ----- session_cached_cursors 200 42%使用率降下来说明缓存有富余了新游标能进得来。但使用率低不等于命中率高还得看实际命中。4.2 对比 session cursor cache hits 增长session cursor cache hits是个累计值要看它的增长速率。做法是间隔一段时间采两次-- 第一次采样 SELECT value FROM v$sysstat WHERE name session cursor cache hits; -- 等待 5 分钟跑一段业务 -- 第二次采样 SELECT value FROM v$sysstat WHERE name session cursor cache hits;两次差值除以时间就是命中速率。调参前如果这个值增长很慢比如 5 分钟才涨几千调参后应该明显变快几万甚至几十万。这个指标比Execute to Parse %更直接因为它就是软软解析的次数。4.3 重算 Execute to Parse %等业务跑了一段时间建议至少一个完整业务周期比如 30 分钟到 1 小时重新算SELECT name, value FROM v$sysstat WHERE name IN (execute count, parse count (total), parse count (hard), session cursor cache hits);然后手算Execute to Parse % (execute count - parse count (total)) / execute count * 100我实测下来一个典型的 OLTP 库把session_cached_cursors从 50 调到 200、同时打开 JDBC 语句缓存后Execute to Parse %从 28% 左右能爬到 75% 以上session cursor cache hits的增长速率翻了十几倍。当然具体数字取决于业务 SQL 的重复度重复度越高提升越明显。4.4 用 AWR 做前后对比如果条件允许调参前后各生成一份 AWR 报告对比 Instance Efficiency Percentages 里的Execute to Parse %以及 Top 5 Timed Foreground Events 里latch: library cache、cursor: pin S这类跟解析相关的等待事件有没有下降。解析少了这些闩锁等待通常会跟着降。提示AWR 对比要注意快照区间要覆盖相似的业务负载。拿凌晨低峰期的报告跟白天高峰期的比没有意义。最好选同一时段的两个快照区间。验证通过后把调参前后的数据记进第 3 节那份 JSON 快照里形成闭环。如果验证下来指标没怎么动别急着继续加参数先看第 5 节的排查清单。5. 本篇常见错排查401、local proxy failed、reading choices 与 OAuth 报错对照调优过程中报错往往不是出在数据库本身而是出在你用什么工具连数据库、用什么通道调模型这一层。尤其是现在很多团队用统一 API 通道比如 TaoToken来管理模型调用和数据库运维脚本的自动化报错信息五花八门。下面按真实遇到的几类报错对照排查。5.1 401 UnauthorizedKey 没配对或过期如果你在用脚本自动拉 AWR、自动执行调参 SQL脚本里通常会配一个 API Key 或数据库凭据。报 401 一般是Key 拼写错误或者复制时带了空格Key 已过期或被轮换请求头里Authorization格式不对比如漏了Bearer前缀。排查动作先把 Key 单独拿出来用最小请求验证。如果是走 TaoToken 的 API 通道Base URL 用https://taotoken.net/apiKey 从控制台重新生成一个Model ID 填你实际要调的模型。这三件套Base URL Key Model ID缺一不可任何一个不对都可能报 401 或 404。5.2 local proxy failed本地代理配置冲突这个报错通常出现在你本地开了某些网络工具或者环境变量里设了HTTP_PROXY/HTTPS_PROXY导致请求被劫持到一个不可用的本地端口。表现是连接超时或connection refused。排查动作# 查看当前代理环境变量 echo $HTTP_PROXY echo $HTTPS_PROXY echo $NO_PROXY # 临时清掉再试 unset HTTP_PROXY HTTPS_PROXY如果是 CI/CD 环境检查 runner 的代理配置。注意这里说的是排查本地代理配置冲突不是让你去配什么特殊网络工具合规环境下直连即可。5.3 reading choices 报错响应结构解析失败reading choices这类报错一般出现在你调模型 API 时返回的 JSON 结构和代码里解析的字段对不上。比如代码里写的是response.choices[0].message.content但实际返回里choices是空数组或者字段名变了。排查动作先把原始响应打印出来别直接取字段。import requests resp requests.post( https://taotoken.net/api/v1/chat/completions, headers{Authorization: Bearer YOUR_KEY}, json{model: YOUR_MODEL_ID, messages: [{role: user, content: hi}]} ) print(resp.status_code) print(resp.text) # 先看原始返回看到原始结构后再决定怎么取字段。常见原因是 Model ID 填错导致返回了错误结构而不是正常补全结果。5.4 OAuth 报错令牌刷新失败如果你用的是 OAuth 方式接入比如某些 CLI 工具走 OAuth 授权报错通常是invalid_grant、token expired或refresh token failed。这类问题的根因往往是refresh token 过期需要重新走一次授权流程系统时间不准导致 JWT 校验失败授权范围scope不够拿不到需要的权限。排查动作先date看系统时间再重新走一遍授权。如果是 Claude Code 这类工具检查它的配置文件里 Base URL、Key、Model ID 是否齐全缺一个都会在授权环节报错。5.5 参数改了没生效忘了重启或 scope 用错回到数据库本身最常见的调了没用是这两种用了scopememory重启后失效用了scopeboth但当前实例不支持动态修改这个参数报ORA-02095: specified initialization parameter cannot be modified。session_cached_cursors是静态参数必须scopespfile 重启。如果你执行alter system set session_cached_cursors200 scopeboth会直接报 ORA-02095。正确做法就是第 3 节写的scopespfile然后重启。把这几类报错对照着排查基本能覆盖调优过程中 90% 的卡住场景。剩下的就是耐心等业务流量跑够用数据说话。6. 把诊断到调优串成闭环TaoToken 通道下的持续观测调参这件事最怕的就是改完就忘。session_cached_cursors从 50 调到 200 只是起点业务在长SQL 在变缓存需求也会变。真正有价值的做法是把它变成一个可重复的观测闭环。我自己的习惯是每周固定拉一次第 2 节那段使用率 SQL 和Execute to Parse %记进一张简单的监控表。如果使用率又爬到 80% 以上或者Execute to Parse %掉回 60% 以下就触发一次复查——看看是不是新上了什么业务、SQL 重复度变了还是连接池配置被谁改了。如果你团队在用 TaoToken 做统一的 API 接入和调用管理可以把这些数据库巡检脚本的触发也挂到同一套通道上。比如用 Coding Plan 跑一个定时任务定期执行巡检 SQL、把结果推到告警或者用模型对话能力对 AWR 报告做一次自动摘要把关键指标变化用自然语言描述出来。这样 DBA 不用天天手工翻报告异常时直接看摘要就行。具体入口想验证模型调用是否正常用模型对话https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite长期跑编码和巡检 Agent看 Coding Planhttps://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite管理 Key 和配额进控制台https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite生成和轮换 API Keyhttps://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文档和参数说明https://taotoken.net/api?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite回到数据库本身最后再强调一个容易忽略的点session_cached_cursors调大之后记得同步检查 PGA 内存。每个会话缓存的游标会占用 PGA虽然单个游标开销不大但几百个会话乘以几百个游标量级也不小。用下面这句看 PGA 使用SELECT name, value, unit FROM v$pgastat WHERE name IN (aggregate PGA target parameter, aggregate PGA auto target, total PGA allocated, maximum PGA allocated);如果total PGA allocated接近aggregate PGA target parameter说明 PGA 吃紧这时候要么调大pga_aggregate_target要么把session_cached_cursors收一收别一味往上加。调优没有一劳永逸只有持续观测、按数据决策。把第 2 节的诊断 SQL、第 3 节的配置、第 4 节的验证方法固化成脚本下次再遇到Execute to Parse %偏低十分钟就能走完整个闭环。
返回列表