1. 一次线上抖动把我拉回 VERSION_COUNT 这个老话题CURSOR_SHARING、VERSION_COUNT和绑定变量这三者的关系是 Oracle SQL 调优里最容易被忽略、又最容易在半夜把你叫起来的一类问题。简单说VERSION_COUNT是V$SQLAREA里同一条 SQL 文本对应的子游标child cursor数量也就是这条 SQL 在共享池里攒了多少个执行计划版本CURSOR_SHARING决定 Oracle 允许多相似的 SQL 共享同一个父游标绑定变量则决定这条 SQL 到底是一条模板复用多次还是每条字面量各占一个坑。这三者一旦配合不好VERSION_COUNT就会膨胀共享池被大量几乎一样的子游标塞满硬解析变多library cache latch等待飙升业务响应时间跟着抖。这篇面向 DBA 和后端开发不讲概念堆砌直接给可复制的查询语句、V$SQL_SHARED_CURSOR诊断脚本和验证动作让你能在真实调优场景里确认版本膨胀到底是不是绑定变量和游标共享策略引起的。适合正在排查共享池压力、latch 争用、或者刚接手一套SQL 写法很随意的老系统的同学。2. 先把 TaoToken 的接入前置准备好我平时做这类诊断习惯把模型对话和 API 调用放在手边遇到不熟的等待事件或参数含义直接问一句比翻文档快。TaoToken 这边接入很直接官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 这个不加 UTM。你需要先去控制台拿 Key地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite Key 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。拿 Key 的步骤不复杂注册登录后进控制台在 API Keys 页面新建一个 Key复制出来存好。如果你只是想先验证模型能不能用、问几个 Oracle 参数问题直接用模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 就行不用写代码。要是你打算长期做 SQL 调优、写诊断脚本、跑 Agent 自动分析 AWR那更适合上 Coding Plan地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 按套餐走比单次调用省心。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite Claude Code 相关的配置看 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite 。注意TaoToken 只是帮你把模型能力接进来的通道真正定位VERSION_COUNT膨胀还得靠下面这些 Oracle 字典视图和脚本。3. 可复制的诊断配置与脚本3.1 先确认当前 CURSOR_SHARING 和共享池状态第一步永远是看现场。别急着改参数先确认当前CURSOR_SHARING是什么值共享池里有没有明显的版本堆积。-- 查看当前 cursor_sharing 设置 show parameter cursor_sharing; -- 或者用字典视图方便脚本化 SELECT name, value, isdefault FROM v$parameter WHERE name cursor_sharing; -- 看共享池整体情况 SELECT pool, name, bytes/1024/1024 AS mb FROM v$sgastat WHERE pool shared pool AND name IN (free memory, miscellaneous);cursor_sharing有三个值EXACT默认精确匹配字面量不同就是不同 SQL、FORCE强制把字面量替换成绑定变量、SIMILAR有柱状图时按绑定变量处理没柱状图时等同 FORCE。这个参数在 11g 之后SIMILAR已经被标记为废弃12c 起官方建议只用EXACT或FORCE这点后面排障会再提。3.2 定位 VERSION_COUNT 偏高的 SQLV$SQLAREA里VERSION_COUNT就是子游标数量。直接按它倒序排谁高谁可疑。-- 找出 VERSION_COUNT 最高的 SQL SELECT sql_id, version_count, executions, parse_calls, loaded_versions, sql_text FROM v$sqlarea WHERE version_count 1 ORDER BY version_count DESC FETCH FIRST 20 ROWS ONLY;如果你在 11g 环境FETCH FIRST不支持换成SELECT * FROM ( ... ORDER BY version_count DESC ) WHERE ROWNUM 20。3.3 用 V$SQL_SHARED_CURSOR 看为什么不共享这是最关键的一步。V$SQL_SHARED_CURSOR会告诉你每个子游标为什么没能和父游标共享每一列对应一个原因值是Y就表示因为这个原因不共享。-- 针对某条 sql_id看每个子游标的不可共享原因 SELECT child_number, reason, address, hash_value FROM v$sql_shared_cursor WHERE sql_id sql_id;reason列是 Oracle 把多个原因拼在一起的字符串常见的有BIND_LENGTH_UPGRADE绑定变量长度变化、BIND_MISMATCH绑定变量类型或数量不一致、OPTIMIZER_MISMATCH优化器环境不同、HASH_MATCH_FAILED、PURGED_CURSOR等。如果你看到BIND_LENGTH_UPGRADE反复出现基本可以锁定是绑定变量长度变化导致的版本膨胀。更细的版本可以逐列查SELECT sql_id, child_number, bind_length_upgrade, bind_mismatch, optimizer_mismatch, stats_row_mismatch, language_mismatch, auth_check_mismatch FROM v$sql_shared_cursor WHERE sql_id sql_id ORDER BY child_number;3.4 判断 SQL 是否真的用了绑定变量有个很实用的小窍门如果sql_text里出现:SYS_B_0这种形式说明这条 SQL 原本没写绑定变量是CURSOR_SHARING帮你替换的。如果看到的是:1、:name这种才是应用自己写的绑定变量。-- 看 SQL 文本里是 SYS_B 还是应用自己的绑定变量 SELECT sql_id, version_count, sql_text FROM v$sqlarea WHERE sql_text LIKE %SYS_B_% AND version_count 1 ORDER BY version_count DESC;3.5 复现实验柱状图 SIMILAR 如何把 VERSION_COUNT 顶上去想亲手验证可以按下面这套走。建一张小表插几行数据然后分别在不同CURSOR_SHARING和统计信息状态下跑同样的字面量 SQL。-- 建测试表 CREATE TABLE test ( name VARCHAR2(50), address VARCHAR2(50) ); INSERT INTO test VALUES (Robinson, ChongQing); INSERT INTO test VALUES (luoluo, China); INSERT INTO test VALUES (luobingsen, Earth); INSERT INTO test VALUES (ROBINSON, YuBei); INSERT INTO test VALUES (bingbing, Tiananmen); INSERT INTO test VALUES (aaaaaa, bbbbbbbbbb); INSERT INTO test VALUES (aaaaaa, bbbbbbbbbbb); COMMIT; -- 清空共享池保证干净起点 ALTER SYSTEM FLUSH SHARED_POOL;然后在CURSOR_SHARINGEXACT下跑七条字面量不同的 SQLSELECT name FROM test WHERE address ChongQing; SELECT name FROM test WHERE address Tiananmen; SELECT name FROM test WHERE address China; SELECT name FROM test WHERE address Earth; SELECT name FROM test WHERE address YuBei; SELECT name FROM test WHERE address bbbbbbbbbb; SELECT name FROM test WHERE address bbbbbbbbbbb;查一下结果SELECT sql_text, version_count FROM v$sqlarea WHERE sql_text LIKE SELECT NAME FROM TEST%;你会看到七条 SQL每条VERSION_COUNT都是 1但它们是七个不同的父游标各自硬解析了一次。这就是EXACT的代价字面量不同完全不共享。接着切到SIMILAR先删掉统计信息再跑ALTER SYSTEM SET cursor_sharing SIMILAR; -- 需要重启或至少让参数生效 EXEC dbms_stats.delete_schema_stats(你的schema名);再跑那七条 SQL查V$SQL你会发现它们还是七条独立 SQLVERSION_COUNT还是 1。原因是没有柱状图SIMILAR此时等同FORCE但没触发替换逻辑Oracle 没有强制绑定。然后收集柱状图EXEC dbms_stats.gather_table_stats( ownname 你的schema名, tabname TEST, cascade FALSE, method_opt for columns address size 2 );再跑那七条 SQL这次查V$SQLSELECT sql_text, hash_value, child_address FROM v$sql WHERE sql_text LIKE SELECT NAME FROM TEST%;你会看到七行sql_text全变成SELECT NAME FROM TEST WHERE ADDRESS:SYS_B_0hash_value相同但child_address各不相同。再查V$SQLAREASELECT sql_text, version_count FROM v$sqlarea WHERE sql_text LIKE SELECT NAME FROM TEST%;VERSION_COUNT变成 7。这就是版本膨胀的现场一条父游标下挂了七个子游标每个子游标一个执行计划。原因是柱状图让 Oracle 认为不同字面量对应的数据分布不同需要各自独立的执行计划于是即使强制绑定了变量还是给每个值生成了独立子游标。最后切到FORCEALTER SYSTEM SET cursor_sharing FORCE; ALTER SYSTEM FLUSH SHARED_POOL;再跑那七条 SQL查V$SQLAREAVERSION_COUNT降回 1。因为FORCE不看柱状图直接按绑定变量共享。4. 验证请求与成功结果诊断做完怎么确认问题真的解决了看三个指标。第一VERSION_COUNT是否回落。针对目标sql_id反复查SELECT sql_id, version_count, executions, parse_calls FROM v$sqlarea WHERE sql_id sql_id;如果version_count从几十降到个位数甚至 1说明子游标不再堆积。第二library cache latch等待是否下降。查 AWR 或实时视图SELECT event, total_waits, time_waited_micro/1000000 AS seconds FROM v$system_event WHERE event LIKE latch: library cache% ORDER BY time_waited_micro DESC;对比调整前后的time_waited如果明显下降说明共享池争用缓解了。第三硬解析比例是否降低。看parse_calls和executions的比值SELECT sql_id, executions, parse_calls, ROUND(parse_calls / GREATEST(executions,1), 4) AS parse_ratio FROM v$sqlarea WHERE sql_id sql_id;parse_ratio越接近 0 越好说明大部分执行都走了软解析。如果你用 TaoToken 的模型对话来辅助分析可以把V$SQL_SHARED_CURSOR的输出贴进去让它帮你归类哪些reason是绑定变量问题、哪些是优化器环境问题。模型对话入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 适合快速判断。要长期跑这类分析脚本Coding Plan 更合适 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。5. 本篇常见错排查5.1 改了 cursor_sharing 没生效cursor_sharing是静态参数改完必须重启实例或者至少让参数在会话级别生效。ALTER SYSTEM SET之后如果没重启当前会话可能还在用旧值。用show parameter cursor_sharing确认别只看ALTER SYSTEM返回成功。5.2 SIMILAR 在 12c 之后行为变了SIMILAR在 11g 就已经不推荐12c 起官方文档明确说它被废弃行为可能和 11g 不一致。如果你在 12c 以上环境看到SIMILAR相关的怪异版本膨胀先确认版本别照搬 11g 的经验。生产环境建议只用EXACT或FORCE。5.3 用了绑定变量但 VERSION_COUNT 还是高这种情况不一定是CURSOR_SHARING的锅。常见原因有绑定变量长度变化触发BIND_LENGTH_UPGRADE不同 schema 下同名 SQL 各自解析优化器环境optimizer_mode、optimizer_features_enable不同数据库 Bug。用V$SQL_SHARED_CURSOR逐列看别只盯着cursor_sharing。5.4 看到 SYS_B 就以为是应用问题SYS_B_0是CURSOR_SHARING替换字面量时生成的系统绑定变量名。看到它说明 SQL 原本没写绑定变量是参数帮你替换的。这时候要回到应用层找那些拼接字面量的 SQL改成真正的绑定变量而不是继续依赖FORCE。5.5 直接上 FORCE 的副作用FORCE能压VERSION_COUNT但它会让所有字面量 SQL 共享同一个执行计划。如果数据分布倾斜严重某些值用同一个计划会走错索引反而更慢。所以FORCE是万不得已的兜底根本解法还是应用层重写 SQL、正确使用绑定变量。6. 接下来怎么走如果你已经用上面的脚本定位到VERSION_COUNT膨胀下一步分两条路。应用能改的去代码里找拼接 SQL 的地方改成绑定变量这是最干净的解法。应用改不动的短期用FORCE压住同时用V$SQL_SHARED_CURSOR持续监控确认没有新的BIND_LENGTH_UPGRADE冒出来。需要把诊断脚本接进自动化流程、或者让模型帮你批量分析 AWR 和V$SQL_SHARED_CURSOR输出可以走 API https://taotoken.net/api Key 在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 拿接入细节看文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。长期做 SQL 调优和 Agent 自动巡检Coding Plan 更划算 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。
阅读完成 · 觉得有帮助?