资讯动态

一次执行选择了错误的索引的研究与困惑:用 TaoToken 统一 Key 复现 DBMS_STATS 统计信息偏差

发布时间:2026/9/29 23:03:01 来源:尧图企业网站定制
1. 一次执行选择了错误的索引问题现场与复现思路优化器统计信息失真导致执行计划选错索引是 Oracle DBA 最头疼的问题之一。我遇到过的典型场景是一张 230 万行的内容表RES_ARTICLE_INFOSQL 里同时有status5、product_id:1、display_time sysdate-90三个条件display_time过滤后返回 13 万到 15 万行product_id的选择性明显更高但优化器偏偏选了DISPLAY_TIME DESC上的函数索引NK_RES_ARTICLE_INFO逻辑读飙升CPU 直接打满。更让人困惑的是同一张表、同一批 SQL在不同时间点收集统计信息后执行计划会在“正确”和“错误”之间反复横跳。这篇文章面向正在排查 Oracle 执行计划选错索引的 DBA 和运维同学也适合需要把 AI 工具接入日常日志与配置排查流程的开发者。我会先复现“统计信息偏差导致选错索引”的完整过程给出可复制的DBMS_STATS收集脚本、错误索引复现 SQL 和执行计划对比动作再说明如何用 TaoToken 统一 Key 把 AI 辅助排查接进这套流程里。核心检索词索引、执行计划、优化器统计信息、DBMS_STATS、游标。下面所有操作都在测试库或可回滚的会话里做生产库执行前先确认有恢复手段。先交代一下问题表的背景。RES_ARTICLE_INFO大约 230 万行DISPLAY_TIME列上有一个降序索引Oracle 会为它生成一个隐藏虚拟列SYS_NC00062$。当DISPLAY_TIME上只收集了基本列统计信息、没有直方图时优化器会假设数据均匀分布而实际上这张表的时间分布极度倾斜近一年每月 3 万到 5 万条越往前越少2004 年之前每月不足 1 万条还混入了少量 1900 年前甚至公元前的异常日期。这种倾斜加上虚拟列统计信息缺失就是执行计划选错索引的温床。2. TaoToken 前置统一 Key 接入 AI 辅助排查排查这类问题时我经常需要把 10053 跟踪文件、执行计划文本、统计信息查询结果丢给 AI 做交叉分析。但不同 AI 工具的接入方式、Key 管理、计费口径都不一样来回切换很费时间。TaoToken 提供的是一个统一 Key / API 通道把模型对话、编码辅助、日志分析这些能力收敛到一套凭证下官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 。它的定位不是替代你的数据库客户端而是给“排查过程中的文本分析”提供一个稳定通道。比如你把 10053 里ix_sel和ix_sel_with_filters的片段贴进去让模型帮你归纳选择性计算的可能路径或者把DBA_TAB_STATS_HISTORY的历史记录整理成表格让模型辅助判断哪次收集引入了偏差。这些都属于辅助分析最终判断仍然由你结合执行计划和实际数据来做。接入前你需要准备两样东西一个可用的 API Key以及确认你的调用方式。Key 在控制台的 API Keys 页面创建地址是 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。如果你更习惯在对话界面里直接贴日志可以用模型对话入口 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。如果你在做长期的编码或 Agent 类工作比如写自动化的统计信息巡检脚本Coding Plan 入口是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite Claude Code 相关配置参考 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_codeutm_campaignrewrite 。注意TaoToken 是 AI 能力接入通道不参与你的数据库连接也不接触生产库数据。贴日志前请自行脱敏去掉真实库名、IP、业务字段值。3. 可复制配置DBMS_STATS 收集脚本与错误索引复现 SQL3.1 先备份当前统计信息建立可回滚点在动任何统计信息之前先确认历史备份可用。10g 之后 Oracle 每次收集统计信息前会自动备份当前版本可以用DBA_TAB_STATS_HISTORY查看备份时间点。-- 查看 RES_ARTICLE_INFO 的统计信息历史 SELECT stats_update_time FROM dba_tab_stats_history WHERE owner USER AND table_name RES_ARTICLE_INFO ORDER BY stats_update_time DESC;如果历史备份不够或者你想手动留一份可以用export_table_stats导出到自己的备份表-- 创建备份表并导出当前统计信息 BEGIN DBMS_STATS.create_stat_table(ownname USER, stattab ZSJ_STAT_BAK); DBMS_STATS.export_table_stats( ownname USER, tabname RES_ARTICLE_INFO, stattab ZSJ_STAT_BAK, statid BAK_BEFORE_FIX ); END; /这一步的意义在于一旦后续收集把执行计划带偏你可以用import_table_stats或restore_table_stats快速回到已知状态。3.2 复现“选错索引”的 SQL 与执行计划对比下面两条 SQL 是当时出问题的精简版本第一条走单表第二条走视图V_173_HANGQING-- SQL 1单表查询display_time 过滤返回约 13 万行 SELECT count(a.image3) FROM res_article_info a WHERE a.status 5 AND a.product_id :1 AND a.display_time sysdate - 90; -- SQL 2视图查询OR 条件涉及 catalog_id 和 brand_id SELECT count(1) FROM v_173_hangqing WHERE product_catalog_id 75450 OR product_brand_id 75450;视图V_173_HANGQING的定义里同样带了display_time sysdate - 90和site_id22等条件。当DISPLAY_TIME和虚拟列SYS_NC00062$上没有直方图时优化器对display_time sysdate-90的选择性估算会严重偏低导致它认为走NK_RES_ARTICLE_INFO的代价比走product_id索引更低。用EXPLAIN PLAN或DBMS_XPLAN对比两次收集前后的计划-- 查看当前执行计划 EXPLAIN PLAN FOR SELECT count(a.image3) FROM res_article_info a WHERE a.status 5 AND a.product_id 672914 AND a.display_time sysdate - 90; SELECT * FROM TABLE(DBMS_XPLAN.display(PLAN_TABLE));如果计划里出现INDEX RANGE SCAN | NK_RES_ARTICLE_INFO而Rows估算值远小于实际返回行数就说明选择性估算出了问题。3.3 纠正统计信息收集指定列和直方图策略问题根源在于size auto对这张表的列判断失误该收集直方图的DISPLAY_TIME和SYS_NC00062$没收集不该收集的PRODUCT_ID反而收集了。纠正方式是显式指定method_opt-- 先删除旧统计信息避免残留 EXEC DBMS_STATS.delete_table_stats(USER, RES_ARTICLE_INFO); -- 按列指定直方图策略虚拟列和 display_time 收集 254 桶product_id 只留基本统计 EXEC DBMS_STATS.gather_table_stats( USER, RES_ARTICLE_INFO, cascade FALSE, estimate_percent 100, method_opt FOR COLUMNS SIZE 254 SYS_NC00062$, DISPLAY_TIME, PRODUCT_ID SIZE 1, force TRUE );收集后确认列统计信息SELECT column_name, num_buckets, histogram, num_distinct, density FROM user_tab_cols WHERE table_name RES_ARTICLE_INFO AND column_name IN (DISPLAY_TIME, SYS_NC00062$, PRODUCT_ID);预期结果是SYS_NC00062$和DISPLAY_TIME的num_buckets为 254、histogram为HEIGHT BALANCEDPRODUCT_ID为NONE。3.4 锁定统计信息并定制收集过程核心业务表的统计信息不应该交给gather_stats_job用默认选项随意收集。先锁定再自己建 job-- 锁定表的统计信息gather_stats_job 将跳过该对象 EXEC DBMS_STATS.lock_table_stats(USER, RES_ARTICLE_INFO); -- 确认锁定状态 SELECT stattype_locked FROM user_tab_statistics WHERE table_name RES_ARTICLE_INFO;然后创建一个存储过程用SIZE REPEAT保持已有的直方图策略CREATE OR REPLACE PROCEDURE proc_gather_stats_res_article IS BEGIN DBMS_STATS.gather_table_stats( USER, RES_ARTICLE_INFO, cascade TRUE, estimate_percent 100, method_opt FOR ALL COLUMNS SIZE REPEAT, force TRUE ); END; /用DBMS_SCHEDULER建 job 调用这个过程按业务低峰期执行。SIZE REPEAT的含义是已有直方图的列保持原有桶数新加的列默认只收集基本统计信息不会因为个别 SQL 触发不必要的直方图收集。4. 验证请求与成功结果执行计划对比与游标失效处理4.1 用 10053 事件确认选择性计算要看清优化器为什么选错索引10053 跟踪是最直接的手段。在会话级别开启ALTER SESSION SET tracefile_identifier idx_fix_check; ALTER SESSION SET events 10053 trace name context forever, level 1; -- 执行目标 SQL加上唯一注释便于定位 SELECT /* idx_fix_check */ count(a.image3) FROM res_article_info a WHERE a.status 5 AND a.product_id 672914 AND a.display_time sysdate - 90; ALTER SESSION SET events 10053 trace name context off;在跟踪文件里搜索Access Path和ix_sel重点看两个值ix_sel表示索引叶块扫描比例ix_sel_with_filters表示回表行数比例。修复前常见的情况是ix_sel极小比如 2.46e-06导致优化器认为扫描代价极低修复后这两个值应该接近实际数据分布。4.2 执行计划对比修复前后各跑一次DBMS_XPLAN对比Rows和Cost-- 修复后查看计划 EXPLAIN PLAN FOR SELECT count(a.image3) FROM res_article_info a WHERE a.status 5 AND a.product_id 672914 AND a.display_time sysdate - 90; SELECT * FROM TABLE(DBMS_XPLAN.display(PLAN_TABLE));修复成功的标志是计划从NK_RES_ARTICLE_INFO切换到IND_ARTINFO_PROD_ID或者第二条 SQL 从单索引扫描切换到INDEX COMBINE使用IND_ARTINFO_PROD_CATAID和IND_ARTINFO_PROD_BRANDID。同时Rows估算值应该接近实际返回行数。4.3 游标失效别忽略 no_invalidate 的默认值这里有一个很容易踩的坑。DBMS_STATS收集统计信息时no_invalidate默认值是DBMS_STATS.AUTO_INVALIDATE意思是相关游标不会立刻失效而是等一段时间后逐渐失效。这样设计是为了避免大量游标集中硬分析造成性能抖动但在你刚修复完统计信息、希望 SQL 立刻用新计划时它反而会拖后腿。我试过在修复后执行 SQL发现计划还是旧的就是因为游标没失效。解决办法有两个-- 方法一收集时显式指定 no_invalidate FALSE EXEC DBMS_STATS.gather_table_stats( USER, RES_ARTICLE_INFO, cascade FALSE, estimate_percent 100, method_opt FOR ALL COLUMNS SIZE REPEAT, no_invalidate FALSE, force TRUE ); -- 方法二对表做一次权限变更强制相关游标失效 GRANT SELECT ON res_article_info TO scott; REVOKE SELECT ON res_article_info FROM scott;方法二看起来有点“野”但在紧急恢复场景下非常有效权限变更会让依赖该对象的游标立即失效下次执行时重新硬分析。操作完成后记得回收不需要的权限。4.4 用 TaoToken 辅助分析跟踪文件10053 跟踪文件动辄几千行人工翻找ix_sel和ix_sel_with_filters很费眼。你可以把关键片段脱敏后贴到模型对话里让 AI 帮你归纳“哪些列的统计信息缺失”“选择性估算偏差出现在哪一步”。入口用 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。如果你在写自动巡检脚本想把跟踪文件解析、异常检测串成流程可以用 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 配合接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 来搭。5. 本篇常见错排查5.1 收集完统计信息执行计划没变最常见的原因是游标没有失效。先确认no_invalidate的设置再检查V$SQL里目标 SQL 的PLAN_HASH_VALUE是否变化。如果没变用权限变更或DBMS_SHARED_POOL.purge强制刷新。5.2 restore_table_stats 之后 CPU 没降下来restore_table_stats只恢复统计信息不会让游标立即失效。默认的AUTO_INVALIDATE会让游标逐步失效所以恢复后短时间内计划可能还是旧的。恢复时加上no_invalidate FALSE或者恢复后手动触发游标失效。5.3 虚拟列 SYS_NC00062$ 没有统计信息降序索引会生成隐藏虚拟列USER_TAB_COL_STATISTICS和USER_TAB_COLUMNS里看不到它需要查USER_TAB_COLS。收集时必须在method_opt里显式写出SYS_NC00062$否则它连基本统计信息都没有优化器只能用默认值估算偏差极大。5.4 size auto 收集了不该收集的直方图size auto会根据列是否出现在WHERE条件中、是否被绑定变量 peeking 到来决定是否收集直方图。这会导致主键列、name这类列被误收集直方图进而引发游标版本激增、library cache latch争用。对核心表用SIZE REPEAT或显式列指定来替代size auto。5.5 异常日期数据导致选择性估算失真表里混入 1900 年前甚至公元前的日期会让LOW_VALUE和HIGH_VALUE跨度极大。没有直方图时优化器假设均匀分布选择性估算会严重偏离。处理方式是先清理异常数据UPDATE res_article_info SET display_time to_date(2000-05-16, yyyy-mm-dd) WHERE display_time to_date(199901, yyyymm) OR display_time to_date(201102, yyyymm); COMMIT;清理后再收集统计信息直方图才能反映真实分布。5.6 ix_sel 和 ix_sel_with_filters 不相等对单列索引理论上这两个值应该相等但在函数索引对应的虚拟列上它们可能不等。ix_sel反映叶块扫描比例ix_sel_with_filters反映回表行数比例。当虚拟列统计信息缺失时ix_sel可能接近 1而ix_sel_with_filters偏小导致代价估算失真。确保虚拟列收集了基本统计信息和直方图是缓解这个问题的关键。6. 把 AI 排查接进日常流程统计信息排查的痛点不在于单次修复而在于“下次还会不会出问题”。我的做法是把这套流程固化下来核心表锁定统计信息用定制 job 按SIZE REPEAT收集每次收集前后用DBMS_XPLAN对比关键 SQL 的计划把 10053 跟踪文件的关键片段脱敏后通过 TaoToken 统一 Key 丢给 AI 做辅助归纳。API Key 在 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 管理接入方式参考 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。如果你也在做长期的数据库巡检或 Agent 类工具Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 可以把模型调用、脚本编排收敛到一套配置里。最后提醒一句任何统计信息操作前先确认DBA_TAB_STATS_HISTORY里有可回滚的备份点生产库上永远给自己留一条退路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑