news 2026/7/30 11:33:31

执行计划一夜之间变了?别查代码了,是统计信息在“说谎“

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
执行计划一夜之间变了?别查代码了,是统计信息在“说谎“

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

有个经典的"凌晨惊魂"场景:某条核心SQL跑了半年都没问题,每天几十万次执行,响应时间稳定在5毫秒以内。某天凌晨三点,监控告警疯狂弹窗——这条SQL突然飙到5秒,CPU打满,整个系统雪崩。

DBA赶到现场,第一反应是看代码有没有人改。没有。看索引有没有人删。没有。看数据量有没有暴涨。也没有。

最后查下来,原因让人无语:统计信息过期了。数据库优化器拿着一份"过时的情报",给这条SQL选了一条错误的执行计划。

今天把执行计划突变的底层原理、排查手段和预防机制,一次讲清楚。


一、先搞懂几个概念

执行计划(Execution Plan):数据库执行一条SQL的具体步骤。先走哪个索引、先JOIN哪张表、用什么JOIN方式(Nested Loop、Hash Join、Merge Join),这些决策组合起来就是执行计划。

优化器(Optimizer):数据库里的"决策引擎",负责为每条SQL选择最优的执行计划。它不跑SQL,只"猜"哪种执行方式最快。

统计信息(Statistics):优化器做决策的依据。包括表的总行数、每列的数据分布(最大值、最小值、NULL占比、直方图)、索引的选择性(不同值的数量)等。本质上就是优化器眼中的"数据库快照"。

基数估计(Cardinality Estimation):优化器预估每一步会返回多少行数据。预估准了,执行计划就优;预估偏了,就可能选错索引、选错JOIN顺序。

CBO(Cost-Based Optimizer):基于成本的优化器。优化器根据统计信息计算每种执行计划的"成本"(CPU消耗、IO次数、内存占用),选成本最低的那个。

理解了这些概念,就能回答一个核心问题:为什么执行计划会突然变?


二、执行计划为什么会"背叛"你

统计信息过期:优化器拿到的是"过期情报"

这是最常见的原因。统计信息不是实时更新的,大多数数据库是定期收集或手动触发。

假设你有一张订单表,平时100万行,统计信息也是按这个量级收集的。某天大促,数据量涨到500万,但统计信息还没更新。优化器依然认为表里只有100万行——于是选择了全表扫描(因为它觉得100万行全表扫比走索引快)。实际上500万行全表扫描直接卡死。

统计信息 ≠ 实时数据,它是一份"延迟的快照"。

数据倾斜:平均值骗了优化器

即使统计信息是新的,也可能因为数据分布不均匀而误导优化器。

比如一个订单表的status列,99%的数据是COMPLETED,1%是PENDING。如果统计信息只记录了平均分布(没有收集直方图),优化器会认为每个状态的占比差不多。当你查询status = 'COMPLETED'时,优化器预估返回1000行(总行数10万的1/100),实际返回99000行——走索引反而比全表扫慢几十倍。

索引变化:新增索引不一定是好事

开发同学看到慢SQL,第一反应是加索引。加完索引后统计信息更新,优化器重新评估所有可用索引,可能选出一个更差的执行计划。

加索引 ≠ SQL变快,它只是给优化器多了一个选择,而这个选择可能是错的。

参数变更:看似无关的配置调整

  • optimizer_modeALL_ROWS改为FIRST_ROWS
  • optimizer_features_enable版本升级后行为变化
  • statistics_levelTYPICAL改为BASIC(停止收集部分统计信息)

这些参数调整不会立刻生效,但下一次硬解析时,优化器的决策逻辑可能完全改变。


三、执行计划突变的排查步骤

第一步:确认是不是执行计划变了

-- Oracle SELECT * FROM v$sql_plan WHERE sql_id = 'your_sql_id'; -- MySQL (8.0+) EXPLAIN FORMAT=TREE SELECT ...; -- PostgreSQL EXPLAIN (ANALYZE, BUFFERS) SELECT ...; -- 对比历史执行计划 -- Oracle: DBMS_XPLAN.DISPLAY_AWR('your_sql_id')

重点对比:访问路径(全表扫 vs 索引扫描)、JOIN顺序、JOIN方式、预估行数 vs 实际行数

第二步:检查统计信息是否过期

-- Oracle:查看表的统计信息收集时间 SELECT table_name, last_analyzed, num_rows, blocks FROM user_tables WHERE table_name = 'YOUR_TABLE'; -- MySQL:查看InnoDB表统计信息 SHOW TABLE STATUS LIKE 'your_table'; -- PostgreSQL:查看统计信息 SELECT last_analyze, last_autoanalyze, n_live_tup, n_dead_tup FROM pg_stat_user_tables WHERE relname = 'your_table';

如果last_analyzed是几天甚至几周前,而这段时间数据变化超过10%,基本可以判定统计信息过期。

第三步:对比预估行数和实际行数

这是判断优化器是否"误判"的关键指标。

-- 执行SQL时开启实际执行统计 -- Oracle: EXPLAIN PLAN FOR ... 然后 SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY); -- MySQL: EXPLAIN ANALYZE SELECT ...; -- PostgreSQL: EXPLAIN ANALYZE SELECT ...;

如果某一步的预估行数(estimated rows)和实际行数(actual rows)相差10倍以上,说明基数估计严重失真,执行计划很可能选错了。

第四步:检查是否有绑定变量窥探问题

绑定变量第一次执行时,优化器会"窥探"变量值来生成执行计划。后续执行直接复用这个计划,即使变量值的数据分布差异很大。

比如第一次传的是status = 'PENDING'(只有100行),优化器选了索引扫描。后面传的是status = 'COMPLETED'(99000行),还是走索引——但全表扫描反而更快。


四、预防执行计划突变的4种手段

手段一:合理设置统计信息收集策略

不要完全依赖自动收集,根据业务特点定制:

策略适用场景收集频率
自动收集 + 默认阈值数据变化平稳的普通表系统自动触发
手动定时收集数据批量导入/删除的表每天凌晨或批量操作后
锁定统计信息历史归档表(数据不变化)收集一次后锁定
收集直方图数据分布严重倾斜的列按需收集
-- Oracle: 手动收集统计信息(含直方图) EXEC DBMS_STATS.GATHER_TABLE_STATS('SCHEMA', 'TABLE_NAME', method_opt => 'FOR COLUMNS SIZE AUTO skewed_column'); -- MySQL: 手动分析表 ANALYZE TABLE your_table; -- PostgreSQL: 手动分析 ANALYZE your_table;

手段二:使用执行计划基线(Plan Baseline)

Oracle提供了SQL Plan Management(SPM),可以把"好的执行计划"锁定下来,即使统计信息变化也不让优化器切换到更差的计划。

-- Oracle: 创建执行计划基线 DECLARE l_plans_loaded PLS_INTEGER; BEGIN l_plans_loaded := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE( sql_id => 'your_sql_id'); END;

金仓数据库也提供了类似的执行计划管理能力。通过DBMS_SPM兼容包,可以将经过验证的优秀执行计划固定下来,避免因统计信息变化导致的性能波动。同时支持执行计划演化(evolve),在确认新计划更优后才自动切换。

手段三:SQL Profile / Outline

SQL Profile是优化器的"纠正器"。当发现某条SQL的执行计划不理想时,可以创建一个SQL Profile,告诉优化器"这条SQL按这个方式执行"。

-- Oracle: 使用SQL TUNING ADVISOR DECLARE l_tuning_task VARCHAR2(30); BEGIN l_tuning_task := DBMS_SQLTUNE.CREATE_TUNING_TASK(sql_id => 'your_sql_id'); DBMS_SQLTUNE.EXECUTE_TUNING_TASK(l_tuning_task); DBMS_SQLTUNE.ACCEPT_SQL_PROFILE(task_name => l_tuning_task); END;

手段四:监控统计信息变化

建立监控机制,在统计信息过期前主动预警:

-- 找出统计信息超过7天未更新的表 SELECT table_name, last_analyzed, ROUND((SYSDATE - last_analyzed), 1) as days_since_analyze FROM user_tables WHERE last_analyzed < SYSDATE - 7 ORDER BY last_analyzed;

建议在监控系统中加入以下告警项:

  • 核心表统计信息超过X天未更新
  • 单表数据变化量超过上次统计的10%
  • 执行计划发生变更(对比AWR报告)

五、总结

执行计划突变的本质,是优化器拿着一份过时的地图,给你指了一条错误的路。

代码没改、索引没动,SQL突然变慢——不要急着翻代码,先查统计信息。

排查执行计划问题,按这个顺序来:

  1. 对比执行计划:确认是不是执行计划变了,不是SQL本身的问题
  2. 检查统计信息:last_analyzed多久了?数据变化量超过10%了吗?
  3. 预估 vs 实际:基数估计偏差超过10倍,优化器就"失明"了
  4. 绑定变量:第一次执行的变量值,可能不适合后续的变量值

预防胜于治疗:统计信息收集策略 + 执行计划基线 + 变化监控,三管齐下,让慢SQL扼杀在摇篮里。


小耶在手,SQL不愁。

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽……我们下次见~

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

从一道经典面试题洞悉 NodeJS 事件循环的核心原理

1 关于 nextTick 回调和 Promise 回调的先后问题 欧莱利曾于 2020 年出版发行过一本介绍分布式系统部署的奇书《Distributed Systems with Node.js》&#xff0c;仔细看过的朋友几乎都建议不要看中文版&#xff0c;理由是翻译常常不能准确传达作者本意&#xff0c;并且书中的实…

作者头像 李华
网站建设 2026/7/30 11:29:52

如何快速掌握TranslucentTB:Windows任务栏透明化的终极指南

如何快速掌握TranslucentTB&#xff1a;Windows任务栏透明化的终极指南 【免费下载链接】TranslucentTB A lightweight utility that makes the Windows taskbar translucent/transparent. 项目地址: https://gitcode.com/gh_mirrors/tr/TranslucentTB 想要让Windows桌面…

作者头像 李华
网站建设 2026/7/30 11:29:14

从经纬度到球面距离:Haversine公式原理与多语言实现指南

1. 项目概述&#xff1a;从“两点之间直线最短”到“地球是个球” 我们从小就知道“两点之间&#xff0c;直线最短”。这个几何公理在平面地图上看起来无比正确&#xff0c;但当我们把目光投向真实世界&#xff0c;尤其是需要跨越成百上千公里时&#xff0c;这个简单的真理就遇…

作者头像 李华
网站建设 2026/7/30 11:24:46

3步解锁RPG Maker加密资源:从新手到专家的完整解密指南

3步解锁RPG Maker加密资源&#xff1a;从新手到专家的完整解密指南 【免费下载链接】RPG-Maker-MV-Decrypter You can decrypt RPG-Maker-MV Resource Files with this project ~ If you dont wanna download it, you can use the Script on my HP: 项目地址: https://gitcod…

作者头像 李华
网站建设 2026/7/30 11:24:31

ICMP协议深度解析:从网络故障排查到安全攻防实战

1. 项目概述&#xff1a;从一次“网络不通”的排查说起 前几天&#xff0c;一个刚入行的同事跑过来问我&#xff0c;说他负责维护的一个内部服务突然访问不了了&#xff0c;ping了一下目标服务器的IP地址&#xff0c;结果返回了一串“Destination Host Unreachable”。他一脸懵…

作者头像 李华