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

发布时间:2026/10/2 11:48:33
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_id1000只有 150 行优化器选索引后面传低选择性的值object_id100有 71679 行索引扫描就变成灾难。反过来也一样第一次传低选择性值走全表扫描后面传高选择性值也全表扫描浪费大量 buffer gets。11gR2 的 Adaptive Cursor SharingACS自适应游标共享就是来解决这个问题的。它让同一条带绑定变量的 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_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_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: fBearer {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, headersheaders, jsonpayload, timeout60) print(resp.json()[choices][0][message][content])这里的关键是三件套Base URL 填https://taotoken.net/apiKey 从控制台生成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_relnone; alter system set _optimizer_extended_cursor_sharingnone; alter system set _optimizer_adaptive_cursor_sharingfalse;注意这些是隐含参数生产环境修改要谨慎改完记得改回来。关闭后重新执行 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_id100 有 71679 行object_id1000 有 150 行object_id10000 有 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 为 FREQUENCYDENSITY 很小。直方图是 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$sqlvar 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 ROWIDINDEX RANGE SCAN因为 1000 只有 150 行走索引合理。此时查看v$sqlselect 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_number0plan_hash_value 对应索引计划executions1IS_BIND_SENSITIVEYIS_BIND_AWARENIS_SHAREABLEY。这里IS_BIND_SENSITIVEY表示启用了绑定变量窥探执行计划取决于变量值IS_BIND_AWAREN表示还没启动扩展游标共享因为目前只有一个绑定值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$sqlchild 0 的 executions 变成 2buffer_gets/executions 涨到 5101 左右。这说明 Oracle 复用了 child 0 的计划但实际逻辑读暴涨因为 100 有 71679 行走索引要回表 7 万多次。关键动作来了再次执行相同的绑定值 100exec :a : 100; select * from ht1 where object_id :a; select * from table(dbms_xplan.display_cursor);这次执行计划变成了TABLE ACCESS FULLplan_hash_value 变了。查看v$sqlselect 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 的 executions2IS_BIND_AWARENchild 1 的 executions1IS_BIND_AWAREYplan_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_number1predicateArange_id0low0.896393high1.095591。这个范围表示当绑定值的选择率落在这个区间时复用 child 1 的全表扫描计划。如果再次执行 100child 1 的 executions 会增加而 child 0 不再增长。为了更完整地观察可以再执行一次 100然后查v$sql_cs_statistics和v$sql_cs_histogramselect 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 的监控组件正在工作。整个链路总结第一次执行 1000child 0 建立IS_BIND_SENSITIVEY第一次执行 100复用 child 0逻辑读暴涨第二次执行 100Oracle 检测到选择性差异创建 child 1IS_BIND_AWAREY执行计划变为全表扫描。这就是 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_AWAREY的 child 才会在v$sql_cs_selectivity里有记录。如果你查的是 child 0而 child 0 的 IS_BIND_AWAREN那自然查不到。另外11gR2 官方文档里居然没有v$sql_cs_selectivity的说明这个视图在 metalink 上有相关 bug 记录比如 Bug 10058195 报告了列被 chr(0) 填充的问题但不影响正常使用。第三类是用 TaoToken API 辅助分析时遇到的报错。如果你在脚本里调用 API可能会看到401 Unauthorized这通常是 API Key 没填对或者过期了。检查Authorization头是不是Bearer 你的KeyKey 有没有多余空格。另一个常见报错是local proxy failed这通常出现在你本地配置了网络代理但代理没有正常转发请求。解决办法是检查本地代理设置或者直接让请求走直连。如果你用的是 Claude Code 或 Cline 这类工具配置 MCP 时可能会遇到reading choices相关的解析错误这通常是返回的 JSON 结构不符合预期检查 base_url 是不是https://taotoken.net/apimodel ID 是不是正确。第四类是 OAuth 相关报错。如果你在配置 Claude Code 的 Anthropic 接入时看到 OAuth 失败注意 Claude Code 的配置里需要同时填 Base URL、Key 和 Model ID 三件套。Base URL 用https://taotoken.net/apiKey 用控制台生成的 API KeyModel 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_sharingEXACT从应用层面正确使用绑定变量。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_AWAREY且 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 个 bucketbucket_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 是否按预期触发。