)
1. 两个让 DBA 又爱又恨的优化器参数如果你在 Oracle 里遇到过「明明建了索引CBO 却偏要走全表扫描」或者「小表驱动大表的连接突然变成 HASH JOIN 把临时表空间打满」那大概率不是统计信息的问题而是OPTIMIZER_INDEX_COST_ADJ和OPTIMIZER_INDEX_CACHING这两个参数在背后捣鬼。它们不改变 SQL 语义也不影响数据本身却直接参与 CBO 对「索引扫描 vs 全表扫描」「NESTED LOOPS vs HASH JOIN」的成本估算属于那种平时没人动、一出事就得翻文档的隐藏开关。OPTIMIZER_INDEX_COST_ADJ的作用是给索引访问路径的成本打一个百分比折扣默认 100 表示「索引成本按原价算」调到 50 就相当于告诉优化器「索引扫描的实际代价只有估算的一半」于是 CBO 会更倾向走索引。OPTIMIZER_INDEX_CACHING则是告诉优化器「索引块有多大比例已经躺在 buffer cache 里」默认 0 表示假设索引块都要从磁盘读调到 90 就相当于说「九成的索引块是缓存命中」逻辑读转换成物理读的估算会大幅下降NESTED LOOPS 和 IN-list 迭代的吸引力随之上升。这两个参数适合谁调面向两类人一类是 DBA手里有 AWR、有执行计划对比权限需要在不改 SQL 的前提下稳住某条关键业务的执行路径另一类是后端工程师SQL 是自己写的但上线后发现计划漂移想通过会话级参数快速验证假设。本文会给出可复制的参数修改 SQL、config.toml骨架、执行计划对比验证动作并说明如何通过 TaoToken 统一 Key 接入 AI 辅助分析工具把执行计划文本丢进去做结构化解读。官网入口见 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 。需要先明确一点这两个参数是「成本修正系数」不是「强制走索引」的开关。它们只影响 CBO 的估算不改变实际执行时的 IO 行为。所以调参之前必须先确认统计信息是新鲜的否则你调的是估算模型而统计信息本身就是错的等于在歪地基上盖楼。2. 前置准备TaoToken 统一 Key 与 AI 分析通道调参过程中最耗时的环节不是改参数而是「改完之后执行计划到底变好了没有」的判断。传统做法是EXPLAIN PLAN加DBMS_XPLAN.DISPLAY然后人肉对比 Cost、Rows、Access Predicates 的差异。当 SQL 有几十行、计划有十几步时肉眼比对很容易漏掉关键变化。我的做法是把执行计划文本和统计信息一起丢给 AI 做结构化对比让它标出「哪一步的 Cost 变化最大」「哪一步的访问路径发生了切换」。要让 AI 工具稳定调用Key 管理是个麻烦事。不同厂商的 API 格式、鉴权头、计费方式都不一样写死在脚本里既不安全也不好维护。TaoToken 提供的是统一 Key 和统一 API 通道一个 Key 可以走多个模型接入文档在 https://taotoken.net/api Key 的创建入口在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。如果你只是想先验证模型对执行计划的理解能力可以直接用模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 贴一段计划进去试。对于需要长期跑 SQL 审查、把执行计划分析做成流水线的场景建议用 Coding Plan入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 它更适合把「取计划 → 调模型 → 输出对比报告」这套动作固化下来。下面给出一份config.toml骨架把 Key 和模型配置集中管理避免散落在各个脚本里。# config.toml - TaoToken 统一接入配置骨架 # 用途Oracle 执行计划分析脚本读取此配置调用模型 [taotoken] # 统一 Key从控制台创建后填入不要提交到版本库 api_key sk-xxxxxxxxxxxxxxxxxxxxxxxx # 统一 API 入口不加 UTM 参数 base_url https://taotoken.net/api # 请求超时执行计划文本较长时适当放大 timeout_seconds 60 [model] # 用于执行计划解读的模型标识 name claude-sonnet # 单次请求最大输出 token max_tokens 4096 # 温度调低保证分析结果稳定可复现 temperature 0.2 [analysis] # 是否在报告中输出每一步的 Cost 变化 diff_cost true # 是否标出访问路径切换INDEX vs TABLE ACCESS FULL diff_access_path true # 是否对比 Rows 估算与实际返回行数 diff_rows true [oracle] # 目标库连接串仅本地调试用生产环境走环境变量 dsn oracle://user:passhost:1521/ORCL # 取执行计划的 SQL_ID 或直接贴 SQL plan_source sql_id配置里的api_key建议通过环境变量注入config.toml只保留占位符。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 有完整的字段说明。如果你用的是 Claude Code 这类编码工具做 SQL 审查Anthropic 兼容通道的配置参考 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_codeutm_campaignrewrite 。3. 可复制配置参数修改 SQL 与作用域选择参数修改分三个作用域会话级、系统级、以及通过ALTER SESSION在特定连接里临时生效。调参验证阶段强烈建议先用会话级确认效果后再决定是否落系统级。会话级修改只影响当前连接不会波及生产库上的其他会话风险可控。先看当前值确认基线-- 查看两个参数的当前生效值 SHOW PARAMETER optimizer_index_cost_adj; SHOW PARAMETER optimizer_index_caching; -- 或者从 v$parameter 查能看到是否被修改过 SELECT name, value, isdefault, default_value FROM v$parameter WHERE name IN (optimizer_index_cost_adj, optimizer_index_caching);会话级修改只影响当前会话-- 让优化器认为索引成本只有估算的一半 ALTER SESSION SET optimizer_index_cost_adj 50; -- 让优化器认为九成索引块已缓存 ALTER SESSION SET optimizer_index_caching 90; -- 确认修改已生效 SELECT name, value FROM v$parameter WHERE name IN (optimizer_index_cost_adj, optimizer_index_caching);系统级修改影响所有新会话需要谨慎-- 系统级修改立即生效于新会话已有会话不受影响 ALTER SYSTEM SET optimizer_index_cost_adj 50 SCOPE MEMORY; ALTER SYSTEM SET optimizer_index_caching 90 SCOPE MEMORY; -- 如果要持久化到 spfile用 SCOPE BOTH ALTER SYSTEM SET optimizer_index_cost_adj 50 SCOPE BOTH;SCOPE MEMORY只改内存重启后失效适合灰度验证SCOPE BOTH同时写 spfile重启后仍生效适合确认稳定后固化。生产环境建议先MEMORY跑一周观察 AWR 里相关 SQL 的执行计划稳定性再决定是否BOTH。参数取值不是越极端越好。OPTIMIZER_INDEX_COST_ADJ调到 10 以下优化器会近乎偏执地走索引遇到选择性差的列反而会放大逻辑读OPTIMIZER_INDEX_CACHING调到 100等于假设索引块永远在缓存里一旦实际发生物理读估算和现实的偏差会非常大。经验区间是OPTIMIZER_INDEX_COST_ADJ在 25 到 75 之间OPTIMIZER_INDEX_CACHING在 50 到 90 之间具体值要靠执行计划对比来定。4. 验证请求执行计划对比与成功结果判定改完参数不能只看「计划变了没有」要看「计划变好没有」。判定标准有三个Cost 是否下降、逻辑读是否减少、访问路径是否符合预期。下面给出一套可复制的对比动作。先准备一条有代表性的 SQL建议选那种「有索引但 CBO 走全表」或者「连接方式不理想」的语句。用EXPLAIN PLAN取修改前的计划-- 修改前取执行计划 EXPLAIN PLAN FOR SELECT a.object_id, b.object_name FROM t a, t b WHERE a.object_id b.object_id AND a.object_id 80000; -- 格式化输出重点看 Cost、Rows、Access Predicates SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format TYPICAL PREDICATE));然后修改参数再取一次计划-- 会话级调整模拟索引高缓存、低成本场景 ALTER SESSION SET optimizer_index_cost_adj 50; ALTER SESSION SET optimizer_index_caching 90; -- 同一条 SQL再取计划 EXPLAIN PLAN FOR SELECT a.object_id, b.object_name FROM t a, t b WHERE a.object_id b.object_id AND a.object_id 80000; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(format TYPICAL PREDICATE));对比两次输出重点看三处第一连接方式是否从HASH JOIN变成NESTED LOOPS第二INDEX FAST FULL SCAN是否变成INDEX RANGE SCAN第三Cost 数值的变化方向。如果连接方式变成 NESTED LOOPS 且 Cost 下降说明参数起到了预期作用。实际执行验证比EXPLAIN PLAN更可靠因为EXPLAIN PLAN不执行 SQL某些绑定变量窥探和自适应计划的行为看不到。用GATHER_PLAN_STATISTICS提示拿真实执行统计-- 真实执行并收集每步统计 SELECT /* GATHER_PLAN_STATISTICS */ a.object_id, b.object_name FROM t a, t b WHERE a.object_id b.object_id AND a.object_id 80000; -- 输出带 A-Rows实际行数和 A-Time 的计划 SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(format ALLSTATS LAST));成功结果的判定A-Rows与E-Rows的偏差在合理范围内通常 10 倍以内Buffers列显示的逻辑读相比修改前明显下降且执行时间没有恶化。如果逻辑读下降但执行时间上升说明索引扫描的随机读代价被低估了需要把OPTIMIZER_INDEX_COST_ADJ往回调。把两次DISPLAY_CURSOR的输出文本保存下来通过 TaoToken 的模型对话入口 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 贴进去让模型逐行对比 Cost、A-Rows、Buffers 的差异能快速定位是哪一步的估算发生了偏移。对于需要批量分析多条 SQL 的场景用 Coding Plan 把「取计划 → 调模型 → 生成对比表」串成脚本更省事。5. 本篇常见错排查5.1 改了参数但执行计划没变最常见的原因是 SQL 文本没变、执行计划被游标缓存复用了。ALTER SESSION改参数后已经硬解析过的游标不会自动失效。解决办法是清掉当前会话的游标缓存或者给 SQL 加一个不影响语义的注释强制重新解析-- 清空当前会话游标缓存 ALTER SESSION SET cursor_sharing EXACT; -- 或者直接刷新共享池生产环境慎用 -- ALTER SYSTEM FLUSH SHARED_POOL; -- 给 SQL 加注释强制生成新游标 SELECT /* tune_2024 */ a.object_id, b.object_name FROM t a, t b WHERE a.object_id b.object_id AND a.object_id 80000;另一个原因是统计信息过期CBO 的基数估算本身就是错的参数修正的是成本系数基数错了系数再准也没用。先跑DBMS_STATS.GATHER_TABLE_STATS刷新统计信息再调参。5.2 OPTIMIZER_INDEX_CACHING 调高后逻辑读反而上升这是典型的「估算与现实的偏差」。参数告诉优化器索引块大部分在缓存里优化器于是选了 NESTED LOOPS但实际执行时驱动表返回的行数远超估算被驱动表被反复探测逻辑读暴涨。排查方法是看DISPLAY_CURSOR里的A-Rows和E-Rows如果驱动表的实际行数是估算的几十倍说明基数估算出了问题应该先修统计信息或加DYNAMIC_SAMPLING提示而不是继续调OPTIMIZER_INDEX_CACHING。5.3 系统级修改后其他业务 SQL 计划漂移ALTER SYSTEM影响所有新会话一条 SQL 调好了另一条可能就崩了。这是参数调优最危险的地方。规避方法是永远先用会话级验证确认只对目标 SQL 有正面影响后再考虑系统级。如果必须系统级用SCOPE MEMORY灰度同时用 SQL Plan Baseline 把关键 SQL 的计划锁住-- 为关键 SQL 加载计划基线防止计划漂移 DECLARE l_plans PLS_INTEGER; BEGIN l_plans : DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE( sql_id your_sql_id_here ); DBMS_OUTPUT.PUT_LINE(Loaded plans: || l_plans); END; /5.4 参数值改了但 v$parameter 显示没变检查是不是改在了错误的容器里。如果是多租户环境ALTER SYSTEM在 CDB 根容器执行只影响根PDB 里的会话读的是 PDB 自己的参数。需要先ALTER SESSION SET CONTAINER your_pdb切到目标 PDB 再改。另外isdefault列如果是TRUE说明当前值就是默认值你改的可能被其他作用域覆盖了。5.5 执行计划对比时 Cost 一样但实际性能差很多Cost 是估算值不是实际耗时。两次计划 Cost 相同但A-Time差异大说明 Cost 模型没有捕捉到真实的 IO 模式差异。这时候要看DISPLAY_CURSOR的Buffers和Reads列物理读和逻辑读的差异才是性能差异的根源。把这两列的数据连同计划一起丢给 AI 分析比只看 Cost 靠谱得多。6. 把调参动作固化成可复用的分析流程参数调优的本质是「提出假设 → 改参数 → 取计划 → 对比验证 → 决定是否固化」。这套动作如果每次靠手工敲 SQL、肉眼比对效率低且容易漏。我的做法是把取计划、调模型、生成对比报告三步串成一个脚本config.toml里配好 TaoToken 的 Key 和模型脚本读配置后自动完成分析。对于只需要临时验证一条 SQL 的场景直接用模型对话入口贴计划文本最快入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。对于要把 SQL 审查纳入日常流程的团队Coding Plan 更适合承载这种重复性分析任务入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。Key 的创建和管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 接入细节查 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。最后提醒一句这两个参数是「微调旋钮」不是「万能开关」。统计信息、索引设计、SQL 写法才是执行计划的地基。地基不稳的时候调参只会让计划在「错」和「更错」之间摇摆。先把统计信息刷新、把该建的索引建上再用这两个参数去修正 CBO 对缓存和 IO 成本的估算偏差才是正确的使用顺序。