1. 从一次执行计划突变说起:Oracle 11g Adaptive Cursor Sharing 到底解决了什么
如果你维护过 Oracle 11g 的库,大概率遇到过这种场景:同一条带绑定变量的 SQL,昨天跑 0.1 秒,今天突然变成 8 秒,执行计划从索引扫描变成了全表扫描,而 SQL 文本一个字都没改。很多人第一反应是统计信息过期,收集完统计信息发现还是老样子。这个现象背后,往往就是绑定变量窥探(Bind Peeking)和执行计划共享之间的矛盾。
Oracle 一直鼓励用绑定变量,好处是减少硬解析、降低共享池压力、缩短解析时间。但绑定变量有个天然缺陷:SQL 第一次执行时,优化器会窥探一次绑定变量的实际值,据此生成一个执行计划,之后所有不同的绑定值都复用这个计划。对于等值查询,这通常没问题;但对于范围查询或者数据分布严重倾斜的列,问题就大了。比如WHERE status = :b,如果第一次传入的是占比 1% 的稀有值,优化器选索引扫描;下次传入占比 60% 的常见值,索引扫描就变成灾难。
Oracle 曾经用CURSOR_SHARING=SIMILAR试图缓解,结果带来更多解析和共享问题,11.1 里就被废弃了。到了 11g,Adaptive Cursor Sharing(ACS)登场,它的思路是:不再强制一条 SQL 只有一个计划,而是让优化器根据绑定变量的实际选择率,动态决定是复用旧计划还是生成新计划。简单说,ACS 让游标在“共享”和“不共享”之间找到一个统计意义上的平衡点。
这篇文章面向的是已经在用 Oracle 11g、被执行计划不稳定困扰的 DBA 和开发。我会先讲清楚 ACS 的触发条件和内部机制,然后给出一套可复制的初始化参数配置和绑定变量测试脚本,最后用数据倾斜场景演示如何通过V$SQL_CS_HISTOGRAM和V$SQL_CS_SELECTIVITY观察 ACS 的实时行为。同时,因为现在很多团队在数据库之外还会用统一的 API 通道来调用模型做 SQL 审核、执行计划解读,我也会顺带演示怎么用 TaoToken 的统一 Key 把这类分析请求接进来,方便你把人工排查和自动化辅助串起来。
ACS 不是银弹,它有明确的适用边界和额外开销。理解它的触发条件,比记住几个视图名字重要得多。下面从机制开始拆。
2. TaoToken 统一 Key 接入:把 SQL 分析请求收口到一个通道
在深入 ACS 之前,先花点篇幅说清楚 TaoToken 在这里扮演什么角色。你可能会问,讲 Oracle 执行计划为什么要提 API 通道?原因很实际:排查 ACS 问题时,我们经常需要把 SQL 文本、执行计划、V$SQL_CS_*视图的输出整理出来,交给模型做模式识别或者生成对比报告。如果每个工具、每个脚本都各自维护一套 Key 和 Base URL,时间一长就是一团乱麻。TaoToken 提供的是统一 Key 和统一 API 入口,让你在多个客户端、多个脚本之间复用同一套凭证。
TaoToken 的官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api 。注意 API 地址后面不加 UTM 参数,保持干净。它的定位是统一的大模型 API 通道,支持对话、编码计划、控制台管理、API Key 管理以及接入文档查询。对于做数据库运维的人来说,比较实用的几个入口是:
- 模型对话:用来快速问执行计划相关的问题,比如“这个计划里 BUFFER SORT 出现在这里意味着什么”
- Coding Plan:如果你在写 PL/SQL 或者 Python 脚本批量采集
V$SQL数据,可以用它辅助生成代码 - 控制台:管理你的 Key、查看用量
- API Keys:生成和轮换密钥
- 接入文档:查具体的请求格式和参数
我试过把 ACS 排查脚本的输出直接拼成 prompt,通过统一 Key 发给模型,让它对比两个 Child Cursor 的选择率范围差异。这样做的好处是,你不需要在每台数据库服务器上配置不同的模型客户端,只要网络能通到 API 入口,用同一个 Key 就能跑。对于有多个环境(开发、测试、生产)的团队,统一 Key 能省掉很多同步配置的麻烦。
需要强调的是,TaoToken 在这里是作为辅助分析通道存在的,它不替代 SQLPlus、不替代 AWR、也不替代你直接查动态性能视图。ACS 的验证必须靠数据库自身的视图和真实执行,模型只能帮你解读和归纳。把这两件事分清楚,后面的操作才不会跑偏。
接入时你需要的三件套是:Base URL(https://taotoken.net/api)、API Key(在控制台生成)、Model ID(按文档选)。这三样在后面的配置片段里会具体出现。如果你只是想做本地 SQL 测试,不接外部通道也完全没问题,ACS 的验证步骤是独立的。
3. 可复制配置:初始化参数、测试表与绑定变量脚本
这一节给出一套可以直接粘贴执行的配置和脚本。目标是在你的 11g 环境里构造出一个 bind sensitive 的 SQL,并让它有机会触发 ACS。先确认你的版本,SELECT * FROM v$version;,本文描述基于 11.2.0.2 及以上、无额外补丁的环境。ACS 相关的隐藏参数和默认行为在不同小版本间有差异,建议先在测试库验证。
3.1 初始化参数与前提检查
ACS 要工作,有几个前提。第一,CURSOR_SHARING必须是EXACT(默认值),SIMILAR已废弃且会干扰 ACS。第二,优化器模式建议用ALL_ROWS。第三,绑定变量窥探必须开启,即_OPTIM_PEEK_USER_BINDS为 TRUE(默认)。检查语句:
-- 检查关键参数 SHOW PARAMETER cursor_sharing; SHOW PARAMETER optimizer_mode; SELECT nam.ksppinm, val.ksppstvl FROM sys.x$ksppi nam, sys.x$ksppcv val WHERE nam.inst_id = val.inst_id AND nam.indx = val.indx AND nam.ksppinm IN ('_optim_peek_user_binds','_optimizer_adaptive_cursor_sharing');如果_optimizer_adaptive_cursor_sharing是 FALSE,ACS 整体关闭,需要改成 TRUE(需要重启或按文档方式调整)。在测试环境可以这样设置:
ALTER SYSTEM SET "_optimizer_adaptive_cursor_sharing"=TRUE SCOPE=SPFILE; -- 重启后生效生产环境改隐藏参数要谨慎,先确认业务影响。cursor_sharing保持 EXACT:
ALTER SYSTEM SET cursor_sharing=EXACT SCOPE=BOTH;3.2 构造数据倾斜的测试表
ACS 的触发依赖数据分布不均。我们建一张订单表,让status列严重倾斜:99% 的行是 'DONE',1% 是 'NEW'。
DROP TABLE t_acs_demo PURGE; CREATE TABLE t_acs_demo ( id NUMBER, status VARCHAR2(10), pad VARCHAR2(100) ); -- 插入 100 万行,其中 99 万 DONE,1 万 NEW INSERT /*+ APPEND */ INTO t_acs_demo SELECT level, CASE WHEN level <= 990000 THEN 'DONE' ELSE 'NEW' END, RPAD('x',100,'x') FROM dual CONNECT BY level <= 1000000; COMMIT; -- 收集统计信息并生成直方图,这是 bind sensitive 的关键 BEGIN DBMS_STATS.GATHER_TABLE_STATS( ownname => USER, tabname => 'T_ACS_DEMO', method_opt => 'FOR ALL COLUMNS SIZE 254', cascade => TRUE ); END; / -- 确认直方图存在 SELECT column_name, histogram, num_buckets FROM user_tab_col_statistics WHERE table_name = 'T_ACS_DEMO';status列应该有 FREQUENCY 或 HEIGHT BALANCED 直方图。没有直方图,等值查询不会成为 bind sensitive,ACS 也就无从触发。
3.3 绑定变量测试脚本
下面这段脚本用同一个游标,先传稀有值 'NEW',再传常见值 'DONE',反复执行,观察 Child Cursor 的变化。注意每次执行后查V$SQL的IS_BIND_SENSITIVE、IS_BIND_AWARE、IS_SHAREABLE。
-- 清理环境 ALTER SYSTEM FLUSH SHARED_POOL; VARIABLE b_status VARCHAR2(10); -- 第一次执行:传入稀有值 NEW EXEC :b_status := 'NEW'; SELECT COUNT(*) FROM t_acs_demo WHERE status = :b_status; -- 查看游标状态 SELECT sql_id, child_number, is_bind_sensitive, is_bind_aware, is_shareable, executions FROM v$sql WHERE sql_text LIKE '%t_acs_demo%status = :b_status%'; -- 第二次执行:传入常见值 DONE EXEC :b_status := 'DONE'; SELECT COUNT(*) FROM t_acs_demo WHERE status = :b_status; -- 再次查看,此时 Child 0 的 histogram 应该出现两个 bucket 都有计数 SELECT sql_id, child_number, is_bind_sensitive, is_bind_aware, is_shareable, executions FROM v$sql WHERE sql_text LIKE '%t_acs_demo%status = :b_status%'; -- 第三次执行:再传 DONE,触发 ACS 生成新 Child EXEC :b_status := 'DONE'; SELECT COUNT(*) FROM t_acs_demo WHERE status = :b_status; -- 观察 Child 数量变化 SELECT sql_id, child_number, is_bind_sensitive, is_bind_aware, is_shareable, executions FROM v$sql WHERE sql_text LIKE '%t_acs_demo%status = :b_status%' ORDER BY child_number;执行到第三次左右,你应该能看到IS_BIND_AWARE从 N 变成 Y,并且出现 child_number 大于 0 的新游标。这就是 ACS 被触发的标志。如果一直是 N,检查直方图是否存在、_optimizer_adaptive_cursor_sharing是否为 TRUE、以及是否执行了足够次数(至少两次以上)。
3.4 通过 TaoToken 统一 Key 接入分析请求的配置片段
如果你想把上面的查询结果自动整理后发给模型做解读,可以用下面这个 JSON 配置。它把 Base URL、Key、Model ID 三件套集中管理,避免散落在各个脚本里。
{ "provider": "taotoken", "base_url": "https://taotoken.net/api", "api_key": "sk-your-taotoken-key-here", "model_id": "your-selected-model-id", "timeout_seconds": 60, "headers": { "Content-Type": "application/json" }, "endpoints": { "chat": "/v1/chat/completions", "models": "/v1/models" } }对应的 TOML 形式,方便你在 Python 或 CLI 工具里读取:
[taotoken] base_url = "https://taotoken.net/api" api_key = "sk-your-taotoken-key-here" model_id = "your-selected-model-id" timeout_seconds = 60 [taotoken.endpoints] chat = "/v1/chat/completions" models = "/v1/models"Key 在控制台生成,Model ID 按接入文档里的列表选。把这段配置放在你的采集脚本旁边,脚本负责查V$SQL_CS_HISTOGRAM,配置负责发请求,职责清晰。注意不要把 Key 硬编码进 SQL 脚本或者提交到版本库。
4. 验证请求与成功结果:观察 V$SQL_CS_HISTOGRAM 与 V$SQL_CS_SELECTIVITY
配置和脚本跑起来之后,关键在验证。ACS 的行为全部记录在几个V$SQL_CS_*视图里,其中V$SQL_CS_HISTOGRAM和V$SQL_CS_SELECTIVITY是最直接的两个。前者按行数分桶记录每个 Child 的执行次数,后者记录每个 Child 对应的绑定变量选择率范围。
4.1 查询 V$SQL_CS_HISTOGRAM
先拿到 sql_id:
SELECT sql_id, child_number, is_bind_sensitive, is_bind_aware, is_shareable FROM v$sql WHERE sql_text LIKE '%t_acs_demo%status = :b_status%' ORDER BY child_number;假设 sql_id 是abc123xyz,查直方图:
SELECT sql_id, child_number, bucket_id, count FROM v$sql_cs_histogram WHERE sql_id = 'abc123xyz' ORDER BY child_number, bucket_id;bucket_id 的含义是:0 表示处理行数小于 1K,1 表示 1K 到 1M 之间,2 表示大于 1M。当 Child 0 在 bucket 0 和 bucket 2 上都有非零计数时,说明同一个计划在不同绑定值下处理的行数差异巨大,这正是 ACS 要介入的信号。你会看到类似这样的输出:
| SQL_ID | CHILD_NUMBER | BUCKET_ID | COUNT |
|---|---|---|---|
| abc123xyz | 0 | 0 | 1 |
| abc123xyz | 0 | 2 | 2 |
| abc123xyz | 1 | 0 | 1 |
| abc123xyz | 1 | 2 | 1 |
Child 0 在 bucket 0 和 bucket 2 都有计数,触发了 ACS;Child 1 是 bind aware 之后生成的新游标,它针对特定选择率范围服务。
4.2 查询 V$SQL_CS_SELECTIVITY
这个视图告诉你每个 Child 负责的选择率区间:
SELECT sql_id, child_number, range_id, low, high FROM v$sql_cs_selectivity WHERE sql_id = 'abc123xyz' ORDER BY child_number, range_id;输出示例:
| SQL_ID | CHILD_NUMBER | RANGE_ID | LOW | HIGH |
|---|---|---|---|---|
| abc123xyz | 1 | 0 | 0.000001 | 0.000100 |
| abc123xyz | 1 | 1 | 0.900000 | 1.000000 |
这表示 Child 1 覆盖了两个选择率区间:极低选择率(稀有值)和极高选择率(常见值)。后续执行时,优化器先算本次绑定变量的选择率,落在哪个区间就用哪个 Child 的计划。如果都不在,就生成新计划、新 Child。
4.3 结合执行计划对比
光看视图还不够,要确认不同 Child 真的用了不同计划:
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('abc123xyz', 0, 'ALLSTATS LAST')); SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('abc123xyz', 1, 'ALLSTATS LAST'));Child 0 大概率是索引扫描(针对稀有值),Child 1 可能是全表扫描或不同的连接方式(针对常见值)。如果两个 Child 的计划完全一样,说明 ACS 虽然生成了新游标,但优化器判断不需要换计划,这也是正常情况。
4.4 通过统一 Key 发送验证请求
把上面的查询结果整理成文本,通过 TaoToken 的对话接口发出去,让模型帮你归纳两个 Child 的选择率差异。请求体大致如下:
{ "model": "your-selected-model-id", "messages": [ { "role": "user", "content": "以下是 Oracle 11g ACS 的两个 Child Cursor 的选择率范围和直方图数据,请分析哪个 Child 服务稀有值、哪个服务常见值,并指出是否存在计划切换风险。数据:Child 0 histogram bucket0=1,bucket2=2; Child 1 selectivity range0=0.000001-0.000100, range1=0.9-1.0。" } ], "temperature": 0.2 }发送到https://taotoken.net/api/v1/chat/completions,带上Authorization: Bearer sk-your-taotoken-key-here。返回结果会给你一个结构化的解读,省去自己对着数字推敲的时间。这一步不是必须的,但如果你要批量处理几十条 SQL 的 ACS 状态,自动化解读会明显提效。
验证成功的标准是:V$SQL里出现IS_BIND_AWARE=Y的 Child,V$SQL_CS_HISTOGRAM里 Child 0 至少两个 bucket 有计数,V$SQL_CS_SELECTIVITY里新 Child 有明确的选择率区间。三者同时满足,说明 ACS 在你的环境里正常工作。
5. 本篇常见报错排查:401、local proxy failed、reading choices 与 OAuth
这一节集中处理两类问题:一类是 ACS 本身不触发,另一类是接入 TaoToken 统一 Key 时遇到的请求错误。两类问题经常混在一起,因为排查 ACS 的脚本可能同时要发 API 请求,报错信息容易让人误判方向。
5.1 ACS 不触发:IS_BIND_AWARE 一直是 N
最常见的原因是直方图缺失。等值查询要成为 bind sensitive,列上必须有直方图。检查:
SELECT column_name, histogram, num_buckets FROM user_tab_col_statistics WHERE table_name = 'T_ACS_DEMO' AND column_name = 'STATUS';如果 histogram 是 NONE,重新收集统计信息,method_opt用FOR COLUMNS STATUS SIZE 254。第二个原因是执行次数不够,ACS 需要至少两次执行、且 Child 0 的直方图出现两个 bucket 同高才触发。第三个原因是_optimizer_adaptive_cursor_sharing被关掉了,用第 3 节的隐藏参数查询确认。第四个原因是CURSOR_SHARING被设成了FORCE或SIMILAR,改回EXACT。
5.2 401 Unauthorized
这是接入 TaoToken 时最直接的错误。原因通常是 Key 缺失、拼写错误、或者请求头格式不对。检查你的请求头:
Authorization: Bearer sk-your-taotoken-key-here注意Bearer和 Key 之间有一个空格,Key 不要带多余引号。如果你用的是 JSON 配置文件,确认api_key字段没有被环境变量覆盖成空值。401 和 ACS 无关,纯粹是凭证问题。在控制台重新生成一个 Key 试试,排除 Key 被禁用或过期的可能。
5.3 local proxy failed
这个报错通常出现在你的脚本或客户端配置了本地代理,但代理进程没起来或者端口不对。TaoToken 的 API 入口是https://taotoken.net/api,如果你的环境里设置了HTTP_PROXY或HTTPS_PROXY环境变量,而代理不可用,就会报 local proxy failed。检查:
echo $HTTP_PROXY echo $HTTPS_PROXY如果不需要代理,清空这两个变量再跑。如果需要,确认代理地址和端口正确。这个错误和数据库无关,是网络层配置问题。
5.4 reading choices 相关报错
这类报错一般出现在解析模型返回结果时,比如你期望返回 JSON,但实际拿到的是流式文本或者错误页。检查两点:一是请求的Content-Type是否为application/json;二是响应状态码是否为 200。如果返回体里包含choices字段但你的解析代码没处理,就会报 reading choices 失败。建议先把原始响应打印出来看结构,再写解析逻辑。Model ID 填错也可能导致返回非预期结构,按接入文档核对。
5.5 OAuth 相关错误
如果你用的是需要 OAuth 流程的客户端,报错可能指向 token 获取失败。TaoToken 的 API Key 方式是直接 Bearer Token,不涉及 OAuth 跳转。如果你在某个工具里看到 OAuth 报错,先确认该工具是否支持 API Key 直连模式。把认证方式从 OAuth 切换到 API Key,填入 Base URL 和 Key 即可。三件套(Base URL、Key、Model ID)缺一不可,任何一项不对都会导致认证或路由失败。
5.6 排查顺序建议
遇到问题先分层:数据库层的问题查V$SQL、V$SQL_CS_*、直方图;网络层的问题查代理、DNS、连通性;认证层的问题查 Key、请求头、Model ID。不要一看到报错就改数据库参数,也不要一看到 ACS 不触发就去换 API Key。分层排查能省很多时间。
6. 把 ACS 验证和统一 Key 接入串成日常流程
ACS 的价值在于它让优化器对绑定变量的选择率有了“记忆”和“分诊”能力,但它的触发条件比较苛刻,需要直方图、需要多次执行、需要数据倾斜。实际生产里,不是每条 SQL 都会走 ACS,你也不需要监控所有 SQL。比较务实的做法是:先找出那些IS_BIND_SENSITIVE=Y且执行频繁的游标,重点观察它们的V$SQL_CS_HISTOGRAM是否出现多 bucket 同高,再决定是否深入。
日常流程可以这样组织:用一段 PL/SQL 或 Python 脚本定期采集V$SQL里 bind sensitive 的游标,把 sql_id、child_number、histogram、selectivity 导出;然后通过 TaoToken 的统一 Key 把整理后的数据发给模型,生成一份变化摘要,比如“本周新增 3 个 bind aware 游标,其中 2 个的选择率区间重叠,存在计划切换风险”。这样你不需要人工盯每个视图,只需要看摘要。
采集脚本里读取配置的部分,用第 3 节的 JSON 或 TOML 片段,把 Base URL 固定为https://taotoken.net/api,Key 从环境变量注入,Model ID 按文档选。这样换环境时只改环境变量,不动脚本。如果你在写批量采集的 Python 代码,可以用 Coding Plan 辅助生成模板,减少重复劳动。
最后提醒一点:ACS 会带来额外的硬解析和 Child Cursor,共享池压力会上升。在 11g 上,如果你的共享池本来就紧张,ACS 触发后可能引发ORA-04031。所以验证 ACS 的同时,也要关注V$SHARED_POOL_ADVICE和V$SQL_SHARED_CURSOR里的不可共享原因。把 ACS 当成一个需要调优的特性,而不是开了就完事的开关。
如果你想把执行计划解读这一步也自动化,可以从模型对话入口进去,把DBMS_XPLAN的输出贴进去,让它标出可能的瓶颈算子。接入文档里有具体的请求示例,照着改 Base URL 和 Key 就行。整套流程跑通后,你手里就有了一条从数据库视图采集、到统一 Key 转发、再到模型解读的链路,排查 ACS 问题时不用再在多个工具之间来回切换。