1. 硬解析飙升时,先搞清楚 FORCE_MATCHING_SIGNATURE 到底在比什么
线上库突然 CPU 打满,AWR 里硬解析每秒几百次,shared pool 里堆着成千上万条长得几乎一样的 SQL——这是很多 DBA 都遇到过的场景。问题通常出在应用没走绑定变量,每条 SQL 的字面量不同,Oracle 就把它们当成完全不同的语句,各生成一份执行计划。要定位这些语句,v$sql里的FORCE_MATCHING_SIGNATURE和EXACT_MATCHING_SIGNATURE是最直接的两个抓手。
先说清楚这两个字段是什么。EXACT_MATCHING_SIGNATURE是对 SQL 文本做精确哈希,只要文本有一个字符不同(包括 where 条件里的字面量),值就不一样。FORCE_MATCHING_SIGNATURE则是在把字面量替换成绑定变量占位符之后再算哈希,所以where id=1、where id=2、where id=3这三条语句的 FORCE 值相同,EXACT 值不同。这个差异就是判断"是否使用绑定变量"的核心依据:当 FORCE 相同而 EXACT 不同时,说明这些语句本可以共用执行计划,却因为字面量差异被拆成了多条硬解析。
适合谁看:日常做 Oracle 性能优化的 DBA、负责 SQL 审核的开发者、以及需要给应用团队提优化建议的运维。你不需要是内核专家,只要能连上库、能查v$sql,就能跟着下面的步骤把硬解析源头筛出来。整篇会交付可直接复制的查询 SQL、cursor_sharing参数的验证方法,以及如何用 TaoToken 统一管理这些诊断脚本的调用通道,避免脚本散落在各台机器上、Key 到处复制。
需要提醒的是,FORCE_MATCHING_SIGNATURE只反映"文本层面能否合并",不代表这些语句一定该合并。有些字面量差异会影响执行计划选择(比如分区键、数据倾斜列),强行合并反而可能让某条语句用错计划。所以筛出来只是第一步,后面还要结合执行计划和业务语义判断。
2. TaoToken 前置:把诊断脚本的模型调用统一到一个 Key
做 SQL 硬解析排查时,除了手工查v$sql,很多人会用脚本自动分析、让模型帮忙解读执行计划或生成优化建议。脚本一多,问题就来了:每台跳板机、每个同事本地都存一份 API Key,模型 ID 写法还不一致,换个人跑就报 401。TaoToken 在这里的作用是提供一个统一的 API 通道,把 Key 和模型配置集中管理,脚本里只引用一个 Base URL 和一个 Key。
TaoToken 是一个大模型 API 聚合网关,兼容 OpenAI 风格的接口协议。你可以把它理解成一个"统一入口":不管底层调的是哪个模型,脚本里写的都是同一套base_url+api_key+model三件套。对 DBA 来说,好处是诊断脚本可以跨机器复用,不用每台机器重新配一遍凭证。
接入前先拿到凭证。打开官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册后,进入控制台创建 API Key:
- 控制台入口:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite
- API Key 管理:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite
创建好 Key 之后,API 的基础地址是https://taotoken.net/api(注意这个地址不加 UTM 参数,直接用于代码里的base_url)。模型 ID 在文档里能查到,常用的对话模型和代码模型都有对应标识:
- 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
- 模型对话体验:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
如果你打算长期跑 SQL 分析类的 Agent 或定时脚本,可以看下 Coding Plan,它更适合高频、持续的调用场景:
- Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
这里要强调一点:TaoToken 只是模型调用的通道,不碰你的数据库。诊断 SQL 还是在本地或跳板机上执行,模型只负责解读你贴过去的执行计划文本。不要把生产库连接串、账号密码发给模型,这是基本纪律。
3. 可复制配置:cursor_sharing 验证与硬解析筛查脚本
这一节给两段能直接用的东西:一段是验证cursor_sharing行为的实验,一段是筛查未使用绑定变量语句的查询。先做实验建立直觉,再上生产查询。
3.1 建测试表并观察签名变化
先建一张小表,插入几条数据,用来复现字面量差异:
create table tb_force(id integer, name varchar2(10)); insert into tb_force(id, name) values ('1','111'); insert into tb_force(id, name) values ('2','222'); insert into tb_force(id, name) values ('3','333'); commit;确认当前cursor_sharing的取值:
show parameter cursor_sharing;默认情况下会看到VALUE是EXACT。在这个模式下,连续执行三条只有 id 不同的语句:
select /*mystatement*/ * from tb_force where id=1; select /*mystatement*/ * from tb_force where id=2; select /*mystatement*/ * from tb_force where id=3;然后查v$sql,对比两个签名字段:
select sql_text, FORCE_MATCHING_SIGNATURE, EXACT_MATCHING_SIGNATURE from v$sql t where sql_text like '%mystatement%';你会看到三条记录的FORCE_MATCHING_SIGNATURE完全相同,而EXACT_MATCHING_SIGNATURE各不相同。这说明在cursor_sharing=EXACT下,这三条语句各自硬解析、各占一份 shared pool 空间,执行计划无法共用。
接着把参数改成 FORCE,清空 shared pool 后重跑:
alter system set cursor_sharing=force; alter system flush shared_pool;再执行那三条查询,然后查v$sql,会发现只剩一条记录,SQL 文本里的字面量被自动替换成了:"SYS_B_0",两个签名字段的值也变得一致。这就是cursor_sharing=force的效果:系统强制把相似语句合并,自动绑定变量。
cursor_sharing三个取值的区别,用一张表对照更清楚:
| 取值 | 行为 | 是否推荐 |
|---|---|---|
| EXACT | 只有文本完全相同的语句才共用游标,默认值 | 推荐,配合应用层绑定变量 |
| SIMILAR | 字面量不同但结构相同的语句尝试共用,除非字面量影响语义或计划 | 不推荐,历史版本 bug 较多 |
| FORCE | 强制把字面量替换为绑定变量后共用游标 | 谨慎,可能影响执行计划选择 |
3.2 筛查未使用绑定变量的语句
理解了签名差异,就能反推:FORCE 相同但 EXACT 不同的语句,就是没走绑定变量的候选。下面这条查询按 FORCE 签名分组,统计每组有多少条不同的 EXACT 签名,数量越多说明字面量差异越严重:
select to_char(FORCE_MATCHING_SIGNATURE) as FORCE_MATCHING_SIGNATURE, count(1) as counts from v$sql where FORCE_MATCHING_SIGNATURE > 0 and FORCE_MATCHING_SIGNATURE <> EXACT_MATCHING_SIGNATURE group by FORCE_MATCHING_SIGNATURE having count(1) > &a order by 2 desc;&a是阈值,比如填 20,就只列出硬解析超过 20 次的签名组。拿到某个FORCE_MATCHING_SIGNATURE值后,再展开看具体是哪些语句:
select t.* from v$sql t where FORCE_MATCHING_SIGNATURE = 16456394970215993993;把上面那个数字换成你实际查出来的值即可。展开后重点看SQL_TEXT、EXECUTIONS、ELAPSED_TIME、PARSE_CALLS这几列,PARSE_CALLS高、EXECUTIONS相对低,说明反复解析、复用差。
3.3 用 TaoToken 统一脚本调用配置
如果你把上面的查询封装成 Python 脚本,让模型自动解读结果,配置可以写成这样(OpenAI 兼容风格):
{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的TaoToken密钥", "model": "你在文档中选定的模型ID", "timeout": 60 }对应的 Python 调用片段:
from openai import OpenAI client = OpenAI( base_url="https://taotoken.net/api", api_key="sk-你的TaoToken密钥" ) resp = client.chat.completions.create( model="你在文档中选定的模型ID", messages=[ {"role": "user", "content": "以下是 Oracle v$sql 查询结果,请帮我判断哪些语句未使用绑定变量:\n" + sql_result_text} ] ) print(resp.choices[0].message.content)把base_url、api_key、model三件套写进配置文件,脚本里只读配置,换机器时改一处即可。这样团队里每个人跑同一份诊断脚本,用的都是同一个通道,不会再出现"我这边能跑你那边 401"的情况。
4. 验证请求:从查询结果到成功定位硬解析源头
配置和 SQL 都就位后,跑一遍完整流程验证。假设你在测试库上已经用 3.1 的实验制造了字面量差异,现在执行 3.2 的分组查询,阈值设成 1:
select to_char(FORCE_MATCHING_SIGNATURE) as FORCE_MATCHING_SIGNATURE, count(1) as counts from v$sql where FORCE_MATCHING_SIGNATURE > 0 and FORCE_MATCHING_SIGNATURE <> EXACT_MATCHING_SIGNATURE group by FORCE_MATCHING_SIGNATURE having count(1) > 1 order by 2 desc;预期结果里会出现一行,COUNTS是 3(对应 id=1、2、3 三条),FORCE_MATCHING_SIGNATURE是一个科学计数法表示的大数。把这个值复制出来,代入展开查询:
select sql_text, executions, parse_calls, elapsed_time from v$sql where FORCE_MATCHING_SIGNATURE = 1.30958144015941E19;注意这里有个坑:FORCE_MATCHING_SIGNATURE在v$sql里是 NUMBER 类型,但显示成科学计数法。直接拿科学计数法的字符串去等值匹配,Oracle 能隐式转换,但更稳妥的写法是用to_char统一格式,或者用to_number转换。如果查不到结果,先确认你复制的值和库里的精度是否一致。
成功的结果应该是:展开后看到三条SQL_TEXT只有 where 条件不同,PARSE_CALLS都是 1 或更高,说明每条都独立解析过。这就定位到了硬解析源头。接下来把SQL_TEXT里的表名、字段名提取出来,反馈给应用团队,让他们改成绑定变量写法,比如把where id=1改成where id=:id。
如果你用 TaoToken 脚本自动分析,把展开查询的结果贴给模型,让它输出"哪些语句建议改绑定变量、哪些因为数据倾斜不建议合并"的判断。请求成功的标志是模型返回了结构化的分析文本,而不是报错。如果返回的是空内容或超时,先检查base_url是否写成了带 UTM 的地址——代码里应该用https://taotoken.net/api,不带任何查询参数。
再补一个验证点:改完应用代码后,重新查分组 SQL,对应的FORCE_MATCHING_SIGNATURE组应该消失,或者COUNTS降到 1。这说明字面量差异消除了,硬解析降下来了。这个前后对比就是优化效果的量化证据。
5. 常见报错排查:401、local proxy failed 与签名查不到
排查过程中最容易卡在几个具体报错上,逐个说清楚。
报错一:401 Unauthorized。脚本调用 TaoToken 时返回 401,通常是 Key 没配对或复制时带了空格。检查api_key字段是不是完整的sk-开头字符串,前后有没有多余换行。另外确认base_url写的是https://taotoken.net/api,如果误写成带/v1或其他路径,也可能导致鉴权失败。Key 是在控制台创建的,如果刚删过旧 Key,记得同步更新所有脚本里的配置。
报错二:local proxy failed 或连接超时。这类报错一般是本机网络环境或代理设置导致的。先确认本机能正常访问https://taotoken.net/api,可以用curl测一下连通性。如果公司网络有出口限制,联系网络管理员放行。注意不要用任何非正规的网络工具去绕过限制,合规访问是前提。脚本里的timeout设成 60 秒比较稳妥,太短容易在模型返回长文本时被截断。
报错三:查询 v$sql 返回空,或 FORCE_MATCHING_SIGNATURE 查不到。几种可能:一是 shared pool 刚被 flush 过,语句还没重新解析,等业务跑一会儿再查;二是权限不够,普通用户看不到v$sql的全部内容,需要SELECT ANY DICTIONARY或 DBA 角色;三是FORCE_MATCHING_SIGNATURE的值精度问题,用to_char转换后再比对。还有一种情况是语句确实都用了绑定变量,那查不到反而是好事。
报错四:reading choices 相关报错。如果你用流式接口,偶尔会看到解析choices字段失败。这通常是返回体被截断或格式异常,检查timeout和网络稳定性,重试一次一般能恢复。如果持续出现,把stream关掉用非流式请求验证。
报错五:OAuth 或鉴权方式混淆。TaoToken 用的是 API Key 鉴权,不是 OAuth 流程。如果你在代码里套了 OAuth 的 token 获取逻辑,会一直失败。直接传api_key即可。Claude Code 这类工具接入时,也是填 Base URL + Key + Model ID 三件套,不要走 OAuth 授权码那套。
排查顺序建议:先确认网络能通,再确认 Key 有效,然后确认base_url和model写对,最后才怀疑数据库侧。大部分问题出在前三步。
6. 把诊断脚本沉淀成可复用的通道
硬解析排查不是一次性的活。今天定位了一批没走绑定变量的语句,过两周新功能上线,可能又冒出来一批。与其每次手工敲 SQL、临时配 Key,不如把 3.2 的查询和 TaoToken 调用封装成一个固定脚本,放进团队的运维工具箱。
具体做法:把分组查询和展开查询写成两个.sql文件,用 shell 或 Python 包一层,输出结果自动发给 TaoToken 的模型做初步分类。配置文件里只保留base_url、api_key、model三个字段,Key 从环境变量读取,不硬编码进脚本。这样换人、换机器,改环境变量就行。
模型选择上,日常解读执行计划用对话模型就够,如果要做复杂的 SQL 改写建议,可以切到代码能力更强的模型。模型 ID 在文档里查,别凭记忆写。长期高频跑的话,Coding Plan 的额度模型更划算,适合挂成定时任务。
最后留一个实用习惯:每次优化完,把优化前后的FORCE_MATCHING_SIGNATURE分组数量记下来,形成趋势。数字往下走,说明绑定变量推广到位;数字反弹,说明有新代码没遵守规范。这比看单次 AWR 报告更能反映长期治理效果。