1. 为什么 alter session 设了 Exact,SQL 还在硬解析
你大概率遇到过这种场景:明明在当前会话里执行了alter session set cursor_sharing=Exact;,按 Oracle 官方说法 Exact 是默认值、要求 SQL 文本完全一致才复用游标,可你去查v$sql,发现同一业务逻辑的语句还是拆成了好几条子游标,parse_calls和loads一路往上涨,硬解析次数根本没降下来。
先把结论摆前面:cursor_sharing=Exact只保证「文本完全相同的 SQL 才共享游标」,它不负责帮你把字面量 SQL 变成绑定变量。也就是说,如果你的应用发过来的是where id=1、where id=3、where id=1这种字面量拼接,Exact 模式下id=1和id=3天然就是两条不同的 SQL,各自硬解析一次,只有第二次出现的id=1才能命中已有游标走软解析。这不是参数没生效,而是 Exact 的语义本来就这样。
真正让人误判的地方在于:很多人把cursor_sharing当成「自动绑定变量开关」,以为设成 Exact 就等于「严格复用」,设成 Force 就等于「强制复用」。实际上 Exact 是「不替换谓词、严格按文本比对」,Force 才是「把所有字面量谓词替换成系统绑定变量再复用」。两者行为差异巨大,选错了就会得到完全相反的解析表现。
这篇就围绕这个排查场景展开:怎么确认会话参数真的生效、怎么用v$sql观察共享池里到底发生了什么、字面量 SQL 和绑定变量 SQL 在 Exact 下分别长什么样,最后给一份可复制的会话检查脚本和config.toml骨架,方便你在统一 Key 通道下把 Oracle 排查和模型辅助串起来。适合正在做 OLTP 调优、被硬解析困扰、又想把排查过程沉淀成可复用配置的 DBA 和后端同学。
2. 前置:TaoToken 统一 Key 通道与排查环境准备
排查 Oracle 硬解析这件事本身不需要联网,但如果你想把排查脚本、报错日志、v$sql输出丢给模型做辅助分析,或者让 coding agent 帮你生成检查 SQL、解读执行计划,就需要一个稳定的模型调用通道。我这边用的是 TaoToken 的统一 Key 通道,一个 Key 打通对话、编码和 API 调用,省得在多个平台之间来回切。
它的定位很简单:官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api ,控制台和 Key 管理在 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console 。如果你只是想让模型帮你读一段tkprof输出,用模型对话页 https://taotoken.net/deep_link?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 就够了;如果是要长期跑编码任务、让 agent 反复生成和修正排查脚本,建议直接上 Coding Plan https://taotoken.net/deep_link?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite ,额度更划算。
环境侧你需要准备的东西不多:一个能连的 Oracle 实例(11g/12c/19c 都行,cursor_sharing行为一致),一个能执行alter session和查v$sql、v$sql_shared_cursor的账号,以及可选的sqlplus或 SQL Developer。下面所有脚本我都实测过,直接改表名就能跑。
注意:
v$sql和v$sql_shared_cursor需要SELECT权限,普通业务账号可能看不到,排查时用有权限的账号,或者让 DBA 开只读视图。
3. 可复制配置:会话参数检查 + v$sql 观察脚本 + config.toml 骨架
3.1 先确认会话参数到底生效没有
很多人第一步就错了:在 A 会话设了参数,却去 B 会话查表现。alter session的作用域仅限当前会话,新开的连接、连接池里复用的其他连接都不受影响。所以第一件事是在同一个会话里确认参数值。
-- 在当前会话执行,确认参数生效 alter session set cursor_sharing=Exact; -- 查当前会话的 cursor_sharing 实际值 select sid, serial#, cursor_sharing from v$session where sid = sys_context('USERENV','SID'); -- 或者更直接,查参数(注意这是实例级默认值,不一定等于会话值) show parameter cursor_sharing;show parameter看到的是实例级默认值,v$session.cursor_sharing才是你这个会话的真实值。如果两者不一致,说明有人改过会话级设置,或者连接池在归还连接时没重置,导致你以为设了 Exact 其实还是 Force。
3.2 用 v$sql 观察共享池里的真实情况
确认参数后,跑几条字面量 SQL,然后观察共享池。下面这段脚本能同时看到 SQL 文本、解析次数、加载次数和是否被强制绑定:
-- 清空共享池前先记录,避免影响生产(测试环境用) -- alter system flush shared_pool; -- 执行三条字面量 SQL select * from jack_exact where id=1; select * from jack_exact where id=3; select * from jack_exact where id=1; -- 观察共享池:Exact 模式下应看到两条不同 SQL select sql_id, sql_text, parse_calls, loads, executions, first_load_time from v$sql where sql_text like 'select * from jack_exact where%' order by first_load_time;Exact 模式下你会看到id=1和id=3各占一行,id=1那行parse_calls可能是 2(第一次硬解析 + 第二次软解析),loads为 1;id=3那行parse_calls为 1、loads为 1。这就是「Exact 只按文本复用」的直接证据。
3.3 用 v$sql_shared_cursor 定位「为什么没共享」
如果两条 SQL 文本看起来一模一样却没共享,问题往往出在v$sql_shared_cursor。这个视图会告诉你每条子游标是因为什么原因无法共享父游标:
select sql_id, child_number, reason, optimizer_mode_mismatch, bind_mismatch, bind_variable_mismatch, force_hard_parse from v$sql_shared_cursor where sql_id in ( select sql_id from v$sql where sql_text like 'select * from jack_exact where%' );重点看bind_mismatch、bind_variable_mismatch和force_hard_parse这几列。如果force_hard_parse是Y,说明有强制硬解析的因素(比如 DDL 后游标失效、cursor_sharing切换);如果bind_mismatch是Y,说明绑定变量类型或长度不一致,这在 Exact 下通常不会出现,但在 Force/Similar 下很常见。
3.4 config.toml 骨架:把排查参数固化下来
如果你用 coding agent 或脚本化方式跑排查,可以把连接信息、观察 SQL、输出路径写进config.toml,避免每次手敲。下面是一份骨架,字段按需改:
# config.toml —— Oracle 硬解析排查配置骨架 [oracle] host = "127.0.0.1" port = 1521 service_name = "ORCLPDB1" user = "system" password = "your_password" # 会话级参数,脚本连接后立即执行 session_params = [ "alter session set cursor_sharing=Exact", "alter session set statistics_level=ALL" ] [observe] # 要观察的目标表 target_table = "JACK_EXACT" # 共享池查询模板 sql_like_pattern = "select * from jack_exact where%" # 输出目录 output_dir = "./oracle_trace" # 是否导出 tkprof enable_tkprof = true [taotoken] # 统一 Key 通道,用于把排查结果交给模型分析 api_base = "https://taotoken.net/api" api_key = "sk-your-key" model = "claude-sonnet" # 模型对话入口,手动分析时用 chat_url = "https://taotoken.net/deep_link?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite"这份配置的核心思路是:把「会话参数」和「观察目标」分离,脚本连上库先执行session_params,再按sql_like_pattern查共享池,最后把结果写到output_dir。需要模型辅助时,把输出文件内容贴到模型对话页,或者用 API 批量分析。
4. 验证请求:Exact 模式下游标复用是否符合预期
4.1 构造对照实验
光看参数不够,得用对照实验证明 Exact 的行为。建一张测试表,插入几行数据,然后分别在 Exact 和 Force 下跑同样的字面量 SQL:
-- 建表并造数据 create table jack_exact (id int, name varchar2(10)); insert into jack_exact values(1,'aa'); insert into jack_exact values(2,'bb'); insert into jack_exact values(3,'cc'); insert into jack_exact values(4,'dd'); commit; -- 场景 A:Exact 模式 alter session set cursor_sharing=Exact; select * from jack_exact where id=1; select * from jack_exact where id=3; select * from jack_exact where id=1; -- 观察结果 select sql_id, sql_text, parse_calls, loads from v$sql where sql_text like 'select * from jack_exact where%';预期结果:两条不同 SQL,id=1的parse_calls=2、loads=1,id=3的parse_calls=1、loads=1。这说明 Exact 下只有文本完全一致才复用,id=1第二次出现走了软解析。
4.2 切到 Force 看差异
-- 场景 B:Force 模式 alter session set cursor_sharing=Force; select * from jack_exact where id=1; select * from jack_exact where id=3; select * from jack_exact where id=1; -- 观察结果 select sql_id, sql_text, parse_calls, loads from v$sql where sql_text like 'select * from jack_exact where%';Force 模式下你会看到 SQL 文本被改写成where id=:"SYS_B_0",三条语句合并成一条,parse_calls=3、loads=1。这就是 Force 的「无条件替换谓词为绑定变量」行为。对比这两个场景,你就能明确判断当前系统的硬解析到底是参数问题还是 SQL 写法问题。
4.3 用 tkprof 验证解析次数
如果想看更细的解析过程,开sql_trace再用tkprof格式化:
alter session set sql_trace=true; -- 执行你的 SQL alter session set sql_trace=false;然后命令行执行:
tkprof /path/to/trace/file.trc out.txt aggregate=no sys=no在out.txt里搜Misses in library cache during parse,值为 1 表示硬解析,0 表示软解析。Exact 模式下id=1第二次出现应该是 0,id=3第一次是 1。这个数字比v$sql更直观,适合写进排查报告。
5. 本篇常见错排查
5.1 参数设了但没生效:作用域搞错
最常见的坑就是alter session只在当前会话有效。如果你用的是连接池(比如 HikariCP、Druid),业务 SQL 跑在池里的其他连接上,你手动设的那个会话根本管不到。解决办法有两个:要么在连接池的connectionInitSql里统一加alter session set cursor_sharing=Exact,要么在实例级用alter system改(但会影响全局,慎用)。
5.2 文本「看起来一样」其实不一样
Exact 是字节级比对,空格、换行、大小写、注释都会导致不共享。比如select * from t where id=1和select * from t where id = 1(等号两边多了空格)在 Exact 下是两条 SQL。排查时用v$sql把sql_text完整拉出来对比,别靠肉眼扫。
5.3 绑定变量类型不一致导致子游标爆炸
即使你用了绑定变量,如果同一个绑定变量在不同调用里传了NUMBER和VARCHAR2,Oracle 会认为是不同的绑定类型,生成不同子游标。查v$sql_shared_cursor的bind_mismatch列能定位。这种情况在 Exact 下也会发生,因为 Exact 不负责统一绑定类型。
5.4 DDL 导致游标失效
对表做 DDL(加列、改索引)后,相关游标会失效,下次执行强制硬解析。这是正常行为,不是参数问题。排查时看v$sql_shared_cursor.force_hard_parse是否为Y,如果是,结合 DDL 时间点判断。
5.5 把 Exact 当成性能优化开关
Exact 是默认值,它本身不优化性能,只是「严格复用」。如果你的系统字面量 SQL 多、硬解析严重,正确做法是改应用用绑定变量,而不是指望 Exact 帮你复用。Force 能缓解但会带来执行计划不稳定的风险,OLTP 场景可以短期用,OLAP 场景应该保持 Exact 并避免绑定变量。
6. 把排查脚本沉淀成可复用通道
排查完一次硬解析问题,最有价值的不是结论,而是那套能重复跑的脚本和配置。我现在的做法是:把config.toml里的session_params和sql_like_pattern按业务模块拆成多份,每次排查换一份配置就行;v$sql和v$sql_shared_cursor的查询语句固定下来,输出直接落盘。
需要模型辅助解读时,把落盘的输出贴到模型对话页 https://taotoken.net/deep_link?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite ,让它帮你归纳「哪些 SQL 在反复硬解析、可能的原因是什么」。如果是长期做数据库调优、想让 agent 自动生成检查脚本并迭代,用 Coding Plan https://taotoken.net/deep_link?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 更顺手。API Key 在控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console 生成,接入细节看文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,Key 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。
最后留一个我踩过的坑:别在排查脚本里随手alter system flush shared_pool,生产环境这一下会把所有游标清掉,瞬间硬解析风暴。测试环境随便玩,生产环境只查不刷。