
1. OLTP 高并发下 cursor_sharing 到底在解决什么问题线上订单库凌晨跑批时AWR 里parse count (hard)一小时涨了 40 万library cache: mutex X等待排到 TOP 3CPU 使用率被硬解析吃掉将近三成。翻v$sql发现同一张订单表上where order_id 1001、 1002、 1003这种只差字面量的语句各自占了一条 parent cursor每条都只执行一次。这就是典型的「字面量 SQL 泛滥」场景而cursor_sharing这个参数正是 Oracle 给这类应用留的一根救命绳。cursor_sharing决定的是什么样的 SQL 语句可以共享同一个游标cursor也就是共享同一份执行计划。它有三个取值EXACT、FORCE、SIMILAR。默认值是EXACT含义最直白——只有 SQL 文本完全一致才允许共享游标。FORCE会把 SQL 里的字面量替换成系统绑定变量形如:SYS_B_0让只差谓词值的语句也能共享父游标。SIMILAR是历史遗留的折中方案行为取决于列上有没有直方图这个后面会重点拆。它适合谁适合那些应用层暂时改不动、SQL 由框架或老代码拼接生成、字面量满天飞的 OLTP 系统。你没法立刻把所有 SQL 改成绑定变量写法但又必须把硬解析压下去这时候cursor_sharing就是过渡期的一根拐杖。但拐杖不能当腿用用错了反而会让执行计划跑偏甚至让本该走索引的查询全表扫描。我先把三种取值的核心差异摆出来后面再逐条验证取值字面量处理游标共享条件直方图影响典型适用EXACT不改写SQL 文本完全相同无规范绑定变量的 OLTPFORCE替换为 :SYS_B_n改写后文本相同无字面量泛滥的过渡期SIMILAR替换为 :SYS_B_n改写后相同且谓词值相同有直方图时退化为 EXACT已不推荐使用需要特别提醒的是Oracle 官方文档里明确写了除非是 DSS 环境否则推荐FORCE因为SIMILAR会导致 child cursor 数量膨胀。而SIMILAR在后续版本里已经被标记为废弃新系统不要再用它。理解它的行为主要是为了看懂老库为什么会出现「明明设了 SIMILAR硬解析还是降不下来」这种怪现象。这一节先把问题定位清楚硬解析高、共享池里一堆只执行一次的游标、mutex 争用严重这三件事同时出现才轮到考虑动cursor_sharing。如果应用本身已经规范使用绑定变量那这个参数保持EXACT就是最优解不要为了「看起来能省解析」去乱改。2. TaoToken 统一 Key 前置准备让 AI 帮你读执行计划排查cursor_sharing的过程中最费时间的不是改参数而是读懂v$sql_shared_cursor里那一长串 Y/N 标志位以及判断某个 child cursor 为什么没被重用。这些视图字段多、含义绕靠人肉翻文档效率很低。我的做法是把执行计划文本和v$sql查询结果丢给大模型让它帮我归纳「这个游标没共享的原因大概率是哪几个标志位」。这里就要用到 TaoToken 的统一 Key 通道。TaoToken 是一个聚合多家模型能力的 API 网关你只需要申请一个 Key就能通过统一的 OpenAI 兼容接口调用不同厂商的模型不用为每个模型单独维护一套鉴权和 SDK。对 DBA 来说它的价值在于把「分析执行计划」这件事变成一个可以脚本化、可以嵌进运维流程的动作而不是每次手动开网页复制粘贴。前置准备分三步。第一步拿到 API Key。访问控制台创建https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite在 API Keys 页面生成一个 Key形如sk-xxxxxxxx。这个 Key 就是后面所有请求的凭证注意不要提交到代码仓库。第二步确认你要用的模型 ID。TaoToken 的模型列表在文档里可以查到https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite第三步记住两个地址。官网入口是https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endAPI 基址是https://taotoken.net/api这个不带 UTM 参数直接用于程序请求。所有对话补全请求都发往https://taotoken.net/api/v1/chat/completions和 OpenAI 的路径结构一致。如果你平时用 Claude Code 做编码辅助TaoToken 也提供了对应的接入方式Base URL 填https://taotoken.net/apiKey 填刚才生成的Model ID 按文档里 Claude 系列的名称填。这样你在终端里让 AI 帮你写排查脚本、解释v$sql_shared_cursor字段就不用切来切去。前置准备的核心就一句话一个 Key、一个 Base URL、一个 Model ID三件套凑齐后面所有 AI 辅助分析都能跑起来。这一步不涉及任何数据库改动纯配置五分钟能搞定。3. 可复制配置cursor_sharing 切换 SQL 与 TaoToken 接入片段这一节给的是可以直接复制粘贴的东西。先看数据库侧的参数切换再看 AI 辅助通道的配置片段。3.1 cursor_sharing 三种取值的切换 SQLcursor_sharing是动态参数支持ALTER SESSION和ALTER SYSTEM两个级别。会话级改动只影响当前连接适合测试系统级改动影响所有新会话生产上要谨慎。会话级切到 FORCE-- 仅当前会话生效退出即恢复 ALTER SESSION SET cursor_sharing FORCE; -- 确认当前会话取值 SHOW PARAMETER cursor_sharing;系统级切到 FORCE只改内存不改 spfile重启后失效ALTER SYSTEM SET cursor_sharing FORCE SCOPE MEMORY;如果要持久化到 spfileALTER SYSTEM SET cursor_sharing FORCE SCOPE BOTH;切回默认 EXACTALTER SYSTEM SET cursor_sharing EXACT SCOPE BOTH;这里有个坑要提前说从 EXACT 切到 FORCE 或 SIMILAR 之前建议先刷两次共享池否则旧的游标残留会让新设置看起来「没生效」。这是很多老 DBA 踩过的坑原因是共享池里还留着 hash 值相同但没被完全清理的游标碎片。-- 连续执行两次确保清理干净 ALTER SYSTEM FLUSH SHARED_POOL; ALTER SYSTEM FLUSH SHARED_POOL;3.2 观测硬解析与游标共享的脚本改完参数得用数据说话。下面这段脚本查硬解析次数和共享池里的游标情况-- 查看各类解析统计 SELECT name, value FROM v$sysstat WHERE name LIKE %parse% ORDER BY name; -- 查看指定 SQL 的 parent/child cursor 分布 SELECT sql_id, sql_text, child_number, executions, plan_hash_value FROM v$sql WHERE sql_text LIKE select * from ta where% ORDER BY sql_id, child_number; -- 查看游标为什么没被共享关键排障视图 SELECT sql_id, child_number, optimizer_mismatch, literal_mismatch, bind_mismatch, load_optimizer_stats, roll_invalid_mismatch FROM v$sql_shared_cursor WHERE sql_id c9swtz4spq3xz;v$sql_shared_cursor里每个字段是 Y/NY 就代表这个原因导致了 child cursor 无法重用。literal_mismatch为 Y通常意味着字面量不同optimizer_mismatch为 Y说明优化器环境或参数变了。3.3 TaoToken 接入配置片段如果你用 Python 脚本把执行计划发给模型分析配置可以写成这样。先建一个taotoken_config.json{ base_url: https://taotoken.net/api, api_key: sk-你的Key, model: 按文档填写的模型ID, timeout: 60 }对应的 Python 调用片段import json import requests with open(taotoken_config.json, r, encodingutf-8) as f: cfg json.load(f) headers { Authorization: fBearer {cfg[api_key]}, Content-Type: application/json } payload { model: cfg[model], messages: [ {role: system, content: 你是 Oracle 性能诊断助手擅长分析执行计划与游标共享问题。}, {role: user, content: 以下是 v$sql_shared_cursor 的查询结果请判断游标未共享的主要原因\n cursor_info} ] } resp requests.post( f{cfg[base_url]}/v1/chat/completions, headersheaders, jsonpayload, timeoutcfg[timeout] ) print(resp.json()[choices][0][message][content])如果你用 Claude Code 做日常编码配置方式是在其设置里指定 Base URL 为https://taotoken.net/api填入同一个 Key 和 Model ID。这样你在写排查脚本时可以直接让 AI 补全 SQL 或解释字段含义。三件套再强调一遍Base URL 是https://taotoken.net/apiKey 是控制台生成的sk-开头字符串Model ID 按文档填。缺一个都跑不通。4. 验证请求从 EXACT 到 FORCE 的硬解析对比实测配置写完必须验证。这一节用一组可复现的测试把 EXACT 和 FORCE 两种模式下的硬解析行为对比清楚。4.1 EXACT 模式下的基线测试先确认当前是默认值SHOW PARAMETER cursor_sharing; -- 预期输出cursor_sharing string EXACT记录当前硬解析次数SELECT name, value FROM v$sysstat WHERE name parse count (hard); -- 假设得到 9890010执行一条带字面量的查询SELECT * FROM ta WHERE id 168;再查硬解析SELECT name, value FROM v$sysstat WHERE name parse count (hard); -- 变成 9890011加 1因为这是首次解析换一个谓词值SQL 结构相同SELECT * FROM ta WHERE id 198;再查硬解析SELECT name, value FROM v$sysstat WHERE name parse count (hard); -- 变成 9890012又加 1结论很清楚EXACT 模式下只要字面量不同就是两条不同的 SQL各自硬解析一次执行计划不共享。这正是高并发 OLTP 里硬解析飙升的根源。4.2 FORCE 模式下的对比测试切到 FORCEALTER SESSION SET cursor_sharing FORCE; SHOW PARAMETER cursor_sharing; -- 预期输出cursor_sharing string FORCE记录基线硬解析SELECT name, value FROM v$sysstat WHERE name parse count (hard); -- 假设得到 9890067执行查询SELECT * FROM ta WHERE id 88;查硬解析SELECT name, value FROM v$sysstat WHERE name parse count (hard); -- 变成 9890068加 1换谓词值SELECT * FROM ta WHERE id 99;再查硬解析SELECT name, value FROM v$sysstat WHERE name parse count (hard); -- 仍然是 9890068没有增加这就是 FORCE 的核心效果Oracle 把id 88和id 99都改写成id :SYS_B_0SQL 文本变得一致于是复用同一个 parent cursor不再硬解析。4.3 用 v$sql 确认改写结果光看硬解析次数还不够得确认字面量真的被替换了SELECT sql_text, child_number, executions FROM v$sql WHERE sql_text LIKE select * from ta where%;预期看到类似select * from ta where id:SYS_B_0 0 2 select * from ta where id:SYS_B_0 1 1 select * from ta where id:SYS_B_0 2 1注意这里出现了多个 child cursor但 parent cursor 是同一个。为什么会有多个 child因为 Oracle 认为已有的游标计划不是最优的于是新建了 child。这时候就要用v$sql_shared_cursor查原因SELECT * FROM v$sql_shared_cursor WHERE sql_id c9swtz4spq3xz;哪个字段是 Y就是哪个原因导致没重用。常见的有optimizer_mismatch优化器参数不同、bind_mismatch绑定变量类型或长度不同。child cursor 不是无限增长的FORCE 会限制它的膨胀这点比 SIMILAR 强。4.4 用 TaoToken 辅助解读结果把上面v$sql_shared_cursor的查询结果整理成文本通过第 3 节的 Python 脚本发给模型让它帮你判断「这几个 child cursor 没共享最可能的原因是什么」。模型会结合字段含义给出排序比你自己一个个查文档快得多。验证模型是否可用可以直接在对话页面测一条https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite实测下来把v$sql_shared_cursor的字段和 Y/N 值一起贴进去模型能比较准确地指出literal_mismatch和optimizer_mismatch的区别省去大量翻文档的时间。5. 本篇常见报错排查401、local proxy failed 与游标不共享这一节把两类问题分开讲一类是 TaoToken 接入时的报错一类是 cursor_sharing 本身的排障。5.1 TaoToken 接入报错401 Unauthorized。这是最常见的。原因通常是 Key 填错、Key 前后有空格、或者请求头格式不对。检查两点一是Authorization头必须是Bearer sk-xxx格式Bearer 和 Key 之间一个空格二是 Key 是否在控制台被删除或过期。重新生成一个 Key 再试https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewritelocal proxy failed / connection refused。这类报错说明请求根本没发到 TaoToken卡在本地网络层。检查你的 Base URL 是不是写成了https://taotoken.net/api/v1/chat/completions之外的东西或者本地有环境变量HTTP_PROXY指向了一个不可用的地址。把代理环境变量清掉再试unset HTTP_PROXY unset HTTPS_PROXYreading choices 报错 / KeyError: choices。这通常不是网络问题而是返回体结构和你预期的不一样。先打印完整响应print(resp.status_code) print(resp.text)如果返回的是错误 JSON里面会有error.message字段说明原因。常见的是 Model ID 填错模型不存在。对照文档里的模型名称重新填。OAuth 相关报错。如果你用的是 Claude Code 这类工具报 OAuth 错误通常是因为工具默认走了自己的鉴权流程没有用你配置的 Base URL 和 Key。检查工具的设置里是否真的把 API 地址改成了https://taotoken.net/api以及是否关闭了它自带的登录流程。5.2 cursor_sharing 排障设了 SIMILAR 但硬解析没降。这是最经典的坑。SIMILAR 的行为取决于列上有没有直方图没有直方图时它等于 FORCE有直方图时它等于 EXACT。所以如果你的表上收集了直方图SIMILAR 就退化成 EXACT字面量不同照样硬解析。验证方法SELECT column_name, num_distinct, num_buckets, histogram FROM dba_tab_col_statistics WHERE table_name TA AND column_name ID;如果histogram列不是NONE那 SIMILAR 就不会按你预期的方式工作。这也是为什么现在不推荐用 SIMILAR。FORCE 下 child cursor 还是很多。用v$sql_shared_cursor查原因重点看optimizer_mismatch、bind_mismatch、load_optimizer_stats这几个字段。如果是bind_mismatch说明绑定变量的类型或长度不一致比如有的传字符串有的传数字。函数索引失效。SIMILAR 模式下Oracle 会把函数索引的参数也转成绑定变量导致索引失效。比如索引是SUBSTR(id,1,3)会被改写成SUBSTR(ID,:SYS_B_0,:SYS_B_1):id索引就用不上了。这是 SIMILAR 的另一个硬伤。星型转换不支持。FORCE 和 SIMILAR 都不支持星型转换如果你的库有数据仓库负载切之前要评估。存储大纲失效。如果之前用 EXACT 生成了 stored outlines切到 FORCE 后这些大纲不会被使用。需要在大纲生成时就设置CREATE_STORED_OUTLINES参数。排障的核心工具就三个v$sysstat看硬解析趋势v$sql看游标分布v$sql_shared_cursor看未共享原因。把这三个视图的查询结果配合 TaoToken 的模型分析基本能覆盖九成以上的游标共享问题。6. 长期编码与 Agent 场景把排查流程固化下来单次排查解决不了长期问题。OLTP 系统的硬解析是持续产生的今天压下去了明天新上线的代码可能又带进来一批字面量 SQL。所以真正有价值的做法是把「观测—分析—告警」这条链路固化成一个可重复执行的流程。我的做法是写一个定时脚本每小时采集一次parse count (hard)的增量超过阈值就触发分析。分析环节把v$sql里执行次数为 1 且sql_text高度相似的语句捞出来整理成文本通过 TaoToken 的接口发给模型让它归纳出「哪些表的哪些查询模式在制造硬解析」。这样你不用天天盯 AWR问题会自己浮出来。如果你需要长期跑这类 Agent 任务TaoToken 的 Coding Plan 提供了更适合持续调用的通道https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite它适合把 AI 分析嵌进日常运维脚本而不是每次手动开对话页面。对于需要反复调用模型做日志归纳、执行计划解读的场景这种按计划订阅的方式比单次调用更省心。回到cursor_sharing本身长期策略应该是把 FORCE 当作过渡手段同时推动应用层改用绑定变量。FORCE 能救急但它改写 SQL 文本的行为会带来副作用比如函数索引失效、执行计划可能因为绑定变量窥探而跑偏。真正健康的 OLTP 系统最终还是要回到 EXACT 加规范绑定变量的路子上。一个实用的技巧在 FORCE 模式下定期用下面这条 SQL 找出「改写后仍然只执行一次」的游标这些就是应用层最该优先改造的 SQLSELECT sql_id, sql_text, executions FROM v$sql WHERE executions 1 AND sql_text LIKE %:SYS_B_% ORDER BY last_load_time DESC;把这些 SQL 交给开发团队比空泛地说「你们要用绑定变量」有说服力得多。数据摆在那里改造的优先级一目了然。最后如果你在接入 TaoToken 或配置 Claude Code 时遇到问题接入文档里有完整的 Base URL、Key、Model ID 三件套说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite把排查脚本、AI 分析、告警阈值这三样东西串起来cursor_sharing 就不再是一个「改完就忘」的参数而是一个可持续观测和优化的入口。