1. 硬解析偏高、执行计划飘忽:先别急着改 SQL
线上库突然 CPU 飙高,library cache里同一类 SQL 的version_count涨到几百,v$sqlarea里一堆只差字面量的语句各占一份游标——这是很多 DBA 遇到过的场景。你打开 AWR,发现硬解析占比很高,执行计划一会儿走索引一会儿全表扫,业务侧反馈"同一条 SQL 昨天快今天慢"。这时候第一反应往往是"应用没写绑定变量",但真去推应用改造,排期动辄几个月,于是目光落到CURSOR_SHARING这个参数上。
CURSOR_SHARING是 Oracle 用来控制"哪些 SQL 可以共享同一个共享游标"的初始化参数,它决定了 Oracle 是否把 SQL 文本里的字面量替换成绑定变量,从而让结构相同、只有常量不同的语句复用游标。它有三个取值:EXACT、SIMILAR、FORCE。听起来是个省事的开关,但它和histogram(直方图)的交互非常微妙——一旦列上有直方图,SIMILAR和FORCE的行为差异会直接体现在执行计划上,甚至制造出大量子游标,把硬解析问题变得更糟。
这篇文章面向正在被硬解析偏高、执行计划不稳定困扰的 DBA。我会先讲清楚CURSOR_SHARING三个取值到底做了什么,再讲histogram如何参与 CBO 的成本计算,然后给出可复制的参数查询、10053trace 开启配置,最后用一组对比实验,让你亲眼看到不同CURSOR_SHARING取值下执行计划怎么变。全程用真实可跑的 SQL,你可以在自己的测试库上跟做。
需要说明的是,CURSOR_SHARING是"止血"手段,不是根治方案。Oracle 官方文档里也明确建议:只要有可能,就应该通过修改应用来正确使用绑定变量。把它当成临时缓解措施,同时推进应用改造,才是稳妥的路径。
2. CURSOR_SHARING 三个取值与 histogram 的交互机制
先把三个取值的语义对齐,这是后面所有诊断的基础。
EXACT是默认值,要求 SQL 文本精确匹配——空格、大小写、字面量都必须完全一致才能共享游标。这意味着where id=1和where id=2是两条不同的 SQL,各自硬解析。在没有直方图的情况下,这种重复硬解析其实没有实质收益,因为执行计划不受数据分布影响,重新解析纯属浪费。
SIMILAR会把未使用绑定变量的语句转换成"类似"绑定变量的形式来共享。但有个关键例外:如果这条 SQL 用到了histogram来生成执行计划,那么它就不会和类似的 SQL 共享了。换句话说,SIMILAR试图在"共享游标"和"保留直方图带来的精确选择率"之间做平衡——对没有直方图的列,它大胆共享;对有直方图的列,它保守地不共享,以免用错选择率。
FORCE则更激进,它和SIMILAR差不多,但即使 SQL 用到了histogram,也会强制采用绑定变量形式共享。这正是问题所在:一旦强制绑定,CBO 在生成执行计划时无法感知具体字面量对应的数据分布,只能用平均选择率(density)来估算,对于数据倾斜严重的列,很容易选错执行计划。
用一个类比来理解:histogram就像一本"数据分布地图",告诉 CBO "值 1 占了 40% 的行,值 75 只占 0.002%"。SIMILAR说"有地图的路线我不合并,免得走错";FORCE说"不管有没有地图,统统合并成一条路线"。对于倾斜列,FORCE合并后 CBO 只能按平均密度估算,可能给一个本该走索引的小结果集选了全表扫描,或者反过来。
从 Oracle 9i 开始引入SIMILAR,官方一度推荐用它替代FORCE。但到了 10g 及以后,实践中发现SIMILAR在直方图列上会产生大量子游标(version_count暴涨),因为每个不同的字面量在有直方图时都不共享,等于硬解析没减少,反而多了游标管理的开销。所以在 10g 中如果发现某条 SQL 的version_count很大,常见原因就是cursor_sharing=SIMILAR且该 SQL 用到了直方图。实际处理时,要么禁用该列的直方图收集,要么把cursor_sharing改成EXACT或FORCE。
还有一个提示符值得记住:CURSOR_SHARING_EXACT。当cursor_sharing为SIMILAR或FORCE时,可以用它让某条 SQL 不走强制绑定,保留原始执行计划:
select /*+ CURSOR_SHARING_EXACT */ object_name from test where object_id = 1;这在个别 SQL 因强制绑定而计划变差时,是个精准的"豁免"手段。
3. 可复制配置:参数查询、直方图查看与 10053 开启
这一节给出你直接能粘贴执行的配置和查询。先确认当前参数值:
show parameter cursor_sharing; select name, value, isdefault, description from v$parameter where name = 'cursor_sharing';查看某条 SQL 的子游标数量,判断是否存在SIMILAR导致的游标膨胀:
select sql_id, sql_text, version_count, executions, parse_calls from v$sqlarea where sql_text like '%object_id%' order by version_count desc;查看列上的直方图信息,确认是频率直方图(Freq)还是高度平衡直方图(Height Balanced),以及 bucket 数量:
select column_name, endpoint_number, endpoint_value, endpoint_actual_value from dba_histograms where table_name = 'TEST' and column_name = 'OBJECT_ID' order by endpoint_number;查看直方图头部统计,DENSITY是 CBO 对非流行值估算选择率时用的密度:
select obj#, col#, bucket_cnt, row_cnt, sample_size, minimum, maximum, distcnt, density from sys.hist_head$ where obj# = (select object_id from dba_objects where object_name = 'TEST' and owner = 'SCOTT') and col# = 4;开启10053trace 观察 CBO 的成本计算过程。注意10053是会话级事件,只对当前会话生效,不会影响其他会话:
alter session set events '10053 trace name context forever, level 1'; select object_name from test where object_id = 1; alter session set events '10053 trace name context off';trace 文件默认落在user_dump_dest(11g 及以前)或diagnostic_dest/trace(12c 及以后)目录下,文件名形如orcl_ora_12345.trc。找到最新生成的那个即可。如果你用的是 12c 以上,可以先查目录:
select value from v$diag_info where name = 'Default Trace File';这个查询会直接告诉你当前会话的 trace 文件路径,省去翻目录的麻烦。
如果你要在测试环境对比不同CURSOR_SHARING取值,可以这样切换(需要相应权限):
alter system set cursor_sharing = force scope = memory; -- 验证 show parameter cursor_sharing; -- 测完改回 alter system set cursor_sharing = exact scope = memory;用scope=memory只改内存,重启后失效,适合临时实验,避免误改 spfile 影响生产。
4. 验证请求:对比不同取值下的执行计划变化
这一节用一组可复现的实验,让你亲眼看到CURSOR_SHARING和histogram如何共同影响执行计划。准备一张数据倾斜的表:
create table test as select object_id, object_name from dba_objects where rownum <= 49909; -- 制造倾斜:让 object_id=1 占大头 update test set object_id = 1 where object_id <= 19928; commit; create index ind_test_object_id on test(object_id); -- 收集带直方图的统计信息 begin dbms_stats.gather_table_stats( ownname => user, tabname => 'TEST', method_opt => 'for columns object_id size 75', cascade => true ); end; /先看object_id=1(流行值,占约 40%)在EXACT下的计划:
alter session set cursor_sharing = exact; alter session set events '10053 trace name context forever, level 1'; select object_name from test where object_id = 1; alter session set events '10053 trace name context off';在 trace 里你会看到Histogram: Freq #Bkts: 75,选择率ix_sel: 0.39929,索引成本约 424,全表扫描成本约 158,最终 CBO 选TableScan。这符合预期——40% 的行走索引不如全表扫。
再看object_id=75(非流行值,只有 1 行):
alter session set events '10053 trace name context forever, level 1'; select object_name from test where object_id = 75; alter session set events '10053 trace name context off';trace 里Card: Rounded: 1,ix_sel: 1.0018e-05(来自 density),索引成本约 2,最终选IndexRange。这就是直方图的价值:CBO 知道 75 是稀有值,走索引。
现在把cursor_sharing切成FORCE,再跑object_id=1:
alter session set cursor_sharing = force; alter session set events '10053 trace name context forever, level 1'; select object_name from test where object_id = 1; alter session set events '10053 trace name context off';关键变化来了:FORCE把字面量1替换成绑定变量:"SYS_B_0",CBO 在生成计划时无法知道这个绑定变量将来会传什么值,只能用平均选择率(density)估算。于是原本该走全表扫描的object_id=1,可能被估算成小结果集而选了索引扫描——执行计划就飘了。反过来,如果先执行的是object_id=75生成的共享游标,之后传入object_id=1,就会复用那个走索引的计划,导致 40% 的行走索引,性能急剧下降。
用v$sql验证绑定变量替换是否发生:
select sql_id, sql_text, version_count, executions from v$sql where sql_text like '%SYS_B%' order by last_active_time desc;你会看到sql_text里出现了:"SYS_B_0",说明FORCE生效了。再对比SIMILAR:在直方图列上,SIMILAR不会共享,所以version_count会随不同字面量增长,硬解析并没有减少。这就是 10g 里SIMILAR常被诟病的原因。
实测下来,判断该用哪个取值,核心看两点:列上有没有直方图、数据是否倾斜。有直方图且倾斜,FORCE风险最大;没直方图,FORCE和SIMILAR都能减少硬解析;SIMILAR在直方图列上等于没共享,还多开销。
5. 本篇常见错排查:401、local proxy failed 与 trace 读不懂
这一节集中处理跟做过程中最容易卡住的几个报错和现象。
报错一:ORA-01031: insufficient privileges执行alter system时。切换cursor_sharing需要ALTER SYSTEM权限,普通用户没有。用sysdba登录,或者让 DBA 帮你执行。如果只是做会话级实验,用alter session set cursor_sharing = force;即可,不需要系统权限,但只对当前会话生效。
报错二:ORA-00942: table or view does not exist查dba_histograms时。dba_histograms需要SELECT ANY DICTIONARY或 DBA 角色。普通用户改用user_histograms,只查自己 schema 下的表:
select column_name, endpoint_number, endpoint_value from user_histograms where table_name = 'TEST' and column_name = 'OBJECT_ID' order by endpoint_number;报错三:10053trace 文件找不到。最常见原因是没查对目录。11g 用show parameter user_dump_dest,12c 及以上用select value from v$diag_info where name = 'Default Trace File';。另外,10053只对当前会话生效,如果你在 A 会话开启、在 B 会话执行 SQL,B 会话不会产生 trace。确保开启和执行在同一个会话里。
现象四:trace 里Histogram: Freq和Histogram: Height Balanced分不清。Freq是频率直方图(基于值),每个 distinct 值占一个 bucket,适合 NDV 较小的列;Height Balanced是高度平衡直方图(基于高度),bucket 数少于 NDV 时使用,每个 bucket 容纳大致相同的行数。判断方法:看#Bkts和NDV的关系,#Bkts >= NDV通常是 Freq,否则是 Height Balanced。
现象五:version_count很高但硬解析没降。这几乎可以确定是cursor_sharing=SIMILAR加直方图列的组合。处理方式三选一:把cursor_sharing改成EXACT(回到精确匹配,靠应用绑定变量)、改成FORCE(接受计划风险换取共享)、或者对该列禁用直方图收集(method_opt => 'for columns object_id size 1')。禁用直方图后,CBO 只能用 density 估算,对倾斜列同样有风险,要权衡。
现象六:local proxy failed或连接层报错。这类报错通常和数据库连接配置、监听、网络层有关,跟CURSOR_SHARING本身无关。排查方向是tnsping、lsnrctl status、检查tnsnames.ora和sqlnet.ora。如果你是通过某种中间层访问数据库,先确认中间层到数据库的连通性,再回到 SQL 层面排查。
现象七:reading choices相关报错。这通常出现在解析 trace 或执行计划输出时,可能是工具版本和数据库版本不匹配。用dbms_xplan.display_cursor直接看执行计划更稳:
select * from table(dbms_xplan.display_cursor('&sql_id', null, 'typical'));把sql_id换成你要查的那条,typical会显示谓词信息和成本,比翻 trace 文件直观。
6. 从诊断到落地:把 CURSOR_SHARING 当止血带而非解药
走完上面这套流程,你应该能回答三个问题:当前cursor_sharing是什么值、目标列上有没有直方图、不同取值下执行计划怎么变。这三个答案决定了你该不该动这个参数。
我的建议是:生产库上优先保持EXACT,把精力放在推动应用使用绑定变量上。CURSOR_SHARING=FORCE只在应用改造遥遥无期、硬解析已经压垮 CPU 的紧急情况下临时启用,并且要配合CURSOR_SHARING_EXACT提示符给关键 SQL 开豁免。SIMILAR在 10g 及以后基本不推荐,它在直方图列上的行为等于没共享,还徒增子游标。
如果你需要一套顺手的工具来辅助排查 SQL 和生成诊断脚本,可以试试 TaoToken 的模型对话能力,把 trace 片段或执行计划贴进去让它帮你解读成本计算路径,比人肉翻文档快不少。地址是 https://taotoken.net/api ,接入文档在 https://taotoken.net/doc ,API Key 在 https://taotoken.net/api-keys 管理。对于长期要做 SQL 调优和 Agent 化诊断的团队,Coding Plan 能省下不少重复劳动,入口在 https://taotoken.net/coding-plan 。
最后留一个实用技巧:每次改完cursor_sharing,别只看单条 SQL 的计划,用v$sqlarea按version_count和parse_calls排序,观察整体硬解析率有没有降。如果version_count反而涨了,说明你踩到了SIMILAR加直方图的坑,赶紧回退。参数是死的,数据分布是活的,定期收集统计信息、监控执行计划漂移,比死守某个参数值更重要。