news 2026/10/2 11:48:30

11gR2 新特性之(一)Adaptive Cursor Sharing(ACS)实战:从绑定变量窥探到执行计划稳定

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
11gR2 新特性之(一)Adaptive Cursor Sharing(ACS)实战:从绑定变量窥探到执行计划稳定

1. 绑定变量场景下执行计划为什么会漂移:11gR2 Adaptive Cursor Sharing 实战复盘

线上 OLTP 系统里,绑定变量是标配。SQL 文本固定、只换绑定值,硬解析次数少、shared pool 压力小,听起来很美好。但只要你用过 Oracle 11g 之前的版本,大概率遇到过这种诡异现象:同一条select * from ht1 where object_id = :a,绑定值传 1000 时走索引范围扫描,快得飞起;换成 100 时还是走索引,逻辑读直接飙到几千,慢到业务告警。这就是绑定变量窥探(Bind Peeking)带来的执行计划漂移。

绑定变量窥探从 9i 就引入了,它的逻辑是:SQL 第一次硬解析时,Oracle 会偷看一眼当前绑定变量的值,用这个值去算选择率、生成执行计划,然后把计划缓存起来。问题在于,后续所有绑定值都复用这个计划。如果第一次传的是高选择性的值(比如 object_id=1000,只有 150 行),优化器选索引;后面传低选择性的值(object_id=100,有 71679 行),索引扫描就变成灾难。反过来也一样,第一次传低选择性值走全表扫描,后面传高选择性值也全表扫描,浪费大量 buffer gets。

11gR2 的 Adaptive Cursor Sharing(ACS,自适应游标共享)就是来解决这个问题的。它让同一条带绑定变量的 SQL 可以拥有多个 child cursor,每个 child cursor 对应一段选择性范围,Oracle 根据绑定值的实际选择性智能判断该复用哪个 child。通俗讲,就是不再“一锤子买卖”,而是根据绑定变量的值动态选择最优执行计划。

这篇文章面向的是正在维护 Oracle 11gR2 OLTP 库的 DBA 和开发人员,尤其是遇到执行计划突然变差、逻辑读暴涨、child cursor 数量异常的场景。我会从测试表构造开始,一步步复现 ACS 生效前后的差异,给出可复制的初始化参数配置、绑定变量测试脚本,以及用v$sql_shared_cursor、v$sql_cs_selectivity等视图验证 ACS 触发链路的完整动作。你跟着做一遍,就能在自己的测试库上看到 child cursor 从 1 个变成 2 个的全过程。

需要说明的是,ACS 在 11gR1 就引入了,但当时 bug 较多,比如 Bug 7213010、Bug 6644714 都报告了 child cursor 数量暴涨的问题,所以没引起太多关注。11gR2 默认开启且相对稳定,才逐渐被重视。理解它的触发链路,对排查 OLTP 绑定变量场景下的计划漂移非常关键。

2. TaoToken 前置准备:用 API 方式快速搭建可复现的 SQL 实验环境

在正式做 ACS 实验之前,先解决一个现实问题:很多同学的测试库要么版本不对,要么权限不够,要么根本连不上。我试过在本地搭一套 11gR2 环境,光安装介质和补丁就折腾半天。如果你只是想快速验证 ACS 的触发逻辑,可以用 TaoToken 的 API 能力来辅助生成测试脚本、解析执行计划输出,把精力集中在实验本身。

TaoToken 是一个面向开发者的模型 API 聚合平台,官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。它提供统一的 API 入口,支持模型对话、Coding Plan、控制台管理、API Keys 管理等能力。对于 DBA 来说,比较实用的场景是:把dbms_xplan.display_cursor的输出贴给模型,让它帮你快速解读执行计划里的 Operation、Rows、Cost 变化;或者让模型根据你的表结构生成绑定变量测试脚本。

API 地址是 https://taotoken.net/api ,注意这个地址不带 UTM 参数,直接用于代码里的 base_url。如果你要在脚本里调用,比如用 Python 写一个批量执行 SQL 并收集执行计划的小工具,可以这样配置:

import requests API_BASE = "https://taotoken.net/api" API_KEY = "你的_API_Key" headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } payload = { "model": "claude-3-5-sonnet", "messages": [ {"role": "user", "content": "帮我解释这段 Oracle 执行计划:TABLE ACCESS FULL 和 INDEX RANGE SCAN 在绑定变量场景下的选择逻辑"} ] } resp = requests.post(f"{API_BASE}/v1/chat/completions", headers=headers, json=payload, timeout=60) print(resp.json()["choices"][0]["message"]["content"])

这里的关键是三件套:Base URL 填https://taotoken.net/api,Key 从控制台生成,Model ID 按你实际使用的模型填写。如果你用的是 Claude Code 这类编码工具,也可以在 settings 里配置:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "你的_API_Key", "ANTHROPIC_MODEL": "claude-3-5-sonnet" } }

配置好之后,你可以让模型帮你做几件事:第一,根据dba_objects的结构生成带数据倾斜的测试表 DDL;第二,把v$sql查询结果整理成表格,对比 child cursor 的IS_BIND_SENSITIVE、IS_BIND_AWARE字段;第三,当遇到ORA-00600或 child cursor 暴涨时,帮你梳理排查思路。这些都不需要你本地有完整的 11gR2 环境,模型可以基于你贴的输出来分析。

需要提醒的是,TaoToken 在这里的角色是辅助工具,不是替代你的数据库。真正的 ACS 实验还是要在 Oracle 实例上跑。如果你还没有 API Key,可以去控制台创建一个:https://taotoken.net/console 。创建后记得把 Key 保存好,后面写脚本会用到。对于长期做数据库运维和脚本开发的场景,可以考虑 Coding Plan,按周期使用更划算:https://taotoken.net/coding-plan 。

3. 可复制配置:11gR2 ACS 相关参数与测试表构造脚本

这一节给出完整的可复制配置。先看 ACS 相关的初始化参数。11gR2 默认开启 ACS 和绑定变量窥探,你可以用下面的命令确认:

show parameter _optimizer_adaptive_cursor_sharing; show parameter _optim_peek_user_binds; show parameter _optimizer_extended_cursor_sharing; show parameter _optimizer_extended_cursor_sharing_rel;

预期输出中,_optimizer_adaptive_cursor_sharing为 TRUE,_optim_peek_user_binds为 TRUE,_optimizer_extended_cursor_sharing为 UDO,_optimizer_extended_cursor_sharing_rel为 SIMPLE。这四个参数共同控制 ACS 的行为。其中_optimizer_adaptive_cursor_sharing是 ACS 总开关,_optim_peek_user_binds控制绑定变量窥探,后两个控制扩展游标共享的模式。

如果你在测试环境想临时关闭 ACS 做对比,可以执行:

alter system set "_optimizer_extended_cursor_sharing_rel"=none; alter system set "_optimizer_extended_cursor_sharing"=none; alter system set "_optimizer_adaptive_cursor_sharing"=false;

注意这些是隐含参数,生产环境修改要谨慎,改完记得改回来。关闭后重新执行 SQL,你会发现 child cursor 不再根据绑定值分裂,执行计划又回到“一锤子买卖”的状态。

接下来构造测试表。为了让数据倾斜明显,我们基于dba_objects创建ht1,然后人为把object_id更新成几个集中值:

create table ht1 as select owner, object_id, object_name from dba_objects; select count(object_id) from ht1; select max(object_id) from ht1; update ht1 set object_id = 100 where object_id < 73405; commit; update ht1 set object_id = 100 where object_id < 73000; commit; update ht1 set object_id = 1000 where object_id > 73000 and object_id < 73300; commit; update ht1 set object_id = 10000 where object_id > 73329; commit; update ht1 set object_id = 10000 where object_id > 70000; commit; select object_id, count(*) from ht1 group by object_id;

执行完你会看到类似结果:object_id=100 有 71679 行,object_id=1000 有 150 行,object_id=10000 有 49 行。这就是典型的数据倾斜:100 的选择性极差,1000 和 10000 的选择性很好。

然后创建索引并收集统计信息,注意method_opt要用for all columns size skewonly,这样 Oracle 会为倾斜列收集直方图:

create index idx_id on ht1(object_id); exec dbms_stats.gather_table_stats(user, 'HT1', method_opt => 'for all columns size skewonly'); select table_name, column_name, density, histogram from user_tab_columns where table_name = 'HT1';

预期看到OBJECT_ID的 HISTOGRAM 为 FREQUENCY,DENSITY 很小。直方图是 ACS 判断选择性的重要依据,没有直方图,ACS 的效果会打折扣。

最后清空 shared pool,确保实验从干净状态开始:

alter system flush shared_pool;

到这里,前置配置就完成了。你可以把上面的 SQL 保存成一个acs_setup.sql文件,用sqlplus / as sysdba执行。整个脚本不超过 30 行,但覆盖了参数确认、测试表构造、索引创建、统计信息收集、shared pool 清理五个关键步骤。后面所有的验证动作都基于这个环境。

4. 验证请求与成功结果:从 child cursor 0 到 child cursor 1 的完整链路

现在开始验证 ACS 的触发链路。先声明绑定变量并赋值为 1000(高选择性),执行查询,然后查看执行计划和v$sql:

var a number; exec :a := 1000; select * from ht1 where object_id = :a; select * from table(dbms_xplan.display_cursor); select hash_value from v$sql where sql_id = '9zq6asm9yfrc9';

注意这里的 sql_id 是示例,你实际执行时要用v$sql查出来的真实 sql_id。执行计划预期是TABLE ACCESS BY INDEX ROWID+INDEX RANGE SCAN,因为 1000 只有 150 行,走索引合理。此时查看v$sql:

select child_number, plan_hash_value, executions, buffer_gets/executions bg_per_ex, is_bind_sensitive bs, is_bind_aware ba, is_shareable s from v$sql where sql_id = '9zq6asm9yfrc9';

预期结果:child_number=0,plan_hash_value 对应索引计划,executions=1,IS_BIND_SENSITIVE=Y,IS_BIND_AWARE=N,IS_SHAREABLE=Y。这里IS_BIND_SENSITIVE=Y表示启用了绑定变量窥探,执行计划取决于变量值;IS_BIND_AWARE=N表示还没启动扩展游标共享,因为目前只有一个绑定值,Oracle 还没观察到选择性差异。

接着把绑定值改成 100(低选择性),再次执行:

exec :a := 100; select * from ht1 where object_id = :a; select * from table(dbms_xplan.display_cursor);

第一次执行 100 时,你会发现执行计划居然还是索引范围扫描,plan_hash_value 和 child 0 一样。查看v$sql,child 0 的 executions 变成 2,buffer_gets/executions 涨到 5101 左右。这说明 Oracle 复用了 child 0 的计划,但实际逻辑读暴涨,因为 100 有 71679 行,走索引要回表 7 万多次。

关键动作来了:再次执行相同的绑定值 100:

exec :a := 100; select * from ht1 where object_id = :a; select * from table(dbms_xplan.display_cursor);

这次执行计划变成了TABLE ACCESS FULL,plan_hash_value 变了。查看v$sql:

select child_number, plan_hash_value, executions, buffer_gets/executions bg_per_ex, is_bind_sensitive bs, is_bind_aware ba, is_shareable s from v$sql where sql_id = '9zq6asm9yfrc9';

预期结果:child 0 的 executions=2,IS_BIND_AWARE=N;child 1 的 executions=1,IS_BIND_AWARE=Y,plan_hash_value 对应全表扫描。这就是 ACS 生效的标志:Oracle 为低选择性值生成了新的 child cursor,并标记为 bind aware。

再查v$sql_cs_selectivity,可以看到 child 1 的选择性范围:

select child_number, predicate, range_id, low, high from v$sql_cs_selectivity where sql_id = '9zq6asm9yfrc9';

预期输出:child_number=1,predicate==A,range_id=0,low=0.896393,high=1.095591。这个范围表示,当绑定值的选择率落在这个区间时,复用 child 1 的全表扫描计划。如果再次执行 100,child 1 的 executions 会增加,而 child 0 不再增长。

为了更完整地观察,可以再执行一次 100,然后查v$sql_cs_statistics和v$sql_cs_histogram:

select child_number, bind_set_hash_value, executions, rows_processed, buffer_gets from v$sql_cs_statistics where sql_id = '9zq6asm9yfrc9'; select child_number, bucket_id, count from v$sql_cs_histogram where sql_id = '9zq6asm9yfrc9' order by child_number;

v$sql_cs_statistics会显示每个 child 的采样执行统计,v$sql_cs_histogram会显示每个 child 的 bucket 计数。从这些视图可以确认 ACS 的监控组件正在工作。

整个链路总结:第一次执行 1000,child 0 建立,IS_BIND_SENSITIVE=Y;第一次执行 100,复用 child 0,逻辑读暴涨;第二次执行 100,Oracle 检测到选择性差异,创建 child 1,IS_BIND_AWARE=Y,执行计划变为全表扫描。这就是 ACS 从绑定变量窥探到敏感度分级再到游标共享的完整触发过程。

5. 本篇常见错排查:401、local proxy failed、reading choices 与 OAuth 报错对照

在实验过程中,你可能会遇到几类报错。第一类是数据库层面的,比如执行dbms_xplan.display_cursor时提示no rows selected,这通常是因为 SQL 还没执行过,或者 shared pool 被清空后 cursor 已经失效。解决办法是先执行一次目标 SQL,再查v$sql拿到 sql_id,然后用dbms_xplan.display_cursor('sql_id', child_number)指定 child 查看。

第二类是 ACS 相关视图查不到数据。比如v$sql_cs_selectivity返回no rows selected,这不一定代表 ACS 没生效,而是因为当前 child cursor 还没有进入 extended cursor sharing 模式。只有IS_BIND_AWARE=Y的 child 才会在v$sql_cs_selectivity里有记录。如果你查的是 child 0,而 child 0 的 IS_BIND_AWARE=N,那自然查不到。另外,11gR2 官方文档里居然没有v$sql_cs_selectivity的说明,这个视图在 metalink 上有相关 bug 记录,比如 Bug 10058195 报告了列被 chr(0) 填充的问题,但不影响正常使用。

第三类是用 TaoToken API 辅助分析时遇到的报错。如果你在脚本里调用 API,可能会看到401 Unauthorized,这通常是 API Key 没填对或者过期了。检查Authorization头是不是Bearer 你的Key,Key 有没有多余空格。另一个常见报错是local proxy failed,这通常出现在你本地配置了网络代理,但代理没有正常转发请求。解决办法是检查本地代理设置,或者直接让请求走直连。如果你用的是 Claude Code 或 Cline 这类工具,配置 MCP 时可能会遇到reading choices相关的解析错误,这通常是返回的 JSON 结构不符合预期,检查 base_url 是不是https://taotoken.net/api,model ID 是不是正确。

第四类是 OAuth 相关报错。如果你在配置 Claude Code 的 Anthropic 接入时看到 OAuth 失败,注意 Claude Code 的配置里需要同时填 Base URL、Key 和 Model ID 三件套。Base URL 用https://taotoken.net/api,Key 用控制台生成的 API Key,Model ID 按实际模型填。如果只填了 Key 没填 Base URL,就会走到默认的 Anthropic 端点,导致 OAuth 或认证失败。

第五类是 child cursor 数量异常暴涨。这是 11gR1 的已知问题,Bug 7213010 和 Bug 6644714 都报告了 ACS 生成大量 child cursor 的情况。在 11gR2 中这个问题有所缓解,但如果你发现v$sql里某个 SQL 的 child 数量超过几十个,可以检查_optimizer_extended_cursor_sharing_rel是不是被改成了 SIMPLE 以外的值,或者绑定变量数量是不是超过了 14 个。根据 metalink 文档 Adaptive Cursor Sharing Overview [ID 740052.1],如果 SQL 里绑定变量超过 14 个,ECS 会被禁用。

另外,如果你修改了cursor_sharing为 similar 或 force,可能会导致大量mutex X waits for cursor等待。建议保持cursor_sharing=EXACT,从应用层面正确使用绑定变量。ACS 本身已经能处理数据倾斜,不需要靠cursor_sharing来强制共享。

排查时的一个实用技巧:用v$sql_shared_cursor查看 child cursor 为什么不能共享。这个视图会列出各种不共享的原因,比如BIND_SENSITIVE、BIND_AWARE、ROLL_INVALID_MISMATCH等。如果某个 child 的BIND_AWARE为 Y,说明它是 ACS 创建的独立 child,不应该被其他绑定值复用。

6. 语义一致 CTA:把 ACS 实验脚本沉淀为可复用的运维能力

ACS 的实验做完之后,建议你把整个流程沉淀成脚本。比如把测试表构造、参数确认、绑定变量执行、视图查询封装成一个acs_demo.sql,下次遇到执行计划漂移的案例,直接改表名和列名就能复现。对于生产环境的排查,可以写一个查询,定期扫描v$sql里IS_BIND_AWARE=Y且 child 数量超过阈值的 SQL,提前发现潜在的游标共享问题。

如果你想把这类脚本生成、执行计划解读、报错排查的工作进一步自动化,可以用 TaoToken 的 API 来辅助。模型对话入口在 https://taotoken.net/api-keys ,接入文档在 https://taotoken.net/doc ,这两个地址分别对应 API Keys 管理和文档说明。对于长期做数据库运维和 Agent 开发的场景,Coding Plan 提供了更稳定的调用方式:https://taotoken.net/coding-plan 。

最后留一个实用技巧:在 11gR2 里,v$sql_cs_histogram每个 child 默认有 3 个 bucket,bucket_id 为 0、1、2。从实验结果看,child 0 的 bucket 1 和 bucket 0 各有 1 次计数,child 1 的 bucket 1 有 2 次计数。这个 bucket 数量是否固定为 3,目前还没有官方文档明确说明,你可以通过更多实验来验证。如果你在实验中发现 child cursor 的行为和预期不一致,优先检查直方图是否收集、绑定变量是否超过 14 个、以及_optimizer_extended_cursor_sharing_rel的取值。这些细节往往决定了 ACS 是否按预期触发。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 11:46:46

电波机芯定制三大核心参数:晶振负载电容、谐振频率与BPC解码容差

电波机芯这个品类&#xff0c;做过的都知道&#xff0c;它不像普通石英钟机芯那样"装上电池就能跑"。很多客户拿着图纸来问&#xff0c;为什么同样的方案&#xff0c;在实验室里走时精准&#xff0c;一到现场就频繁跳秒、对时失败、甚至整批返修。我这些年经手的定制…

作者头像 李华