连载中 6/22

优化器的选择:统计信息、成本估算与 force index

2026-05-04 · 8460 阅读 · 0 评论 · 0 赞

它不是不会,是在算账

修复完所有失效写法,老王又报了个新怪:同一条 SQL,上午走索引,晚上全表扫;测试环境飞快,生产环境偶尔抽风。先说结论:这不是 Bug,是优化器在按成本选方案——它每次都重新算账,数据变了、账变了,选择就变了。这篇拆它的账本。

账本之一:统计信息

优化器不数全表,靠统计信息估算。InnoDB 默认开启 innodb_stats_persistent:表和索引的统计(行数、基数)持久化存盘,由两部分数据构成——每次数据变更累积到 10%(innodb_stats_auto_recalc)触发自动重算,以及手动 ANALYZE TABLE 表名

重点:统计不是精确数,是抽样——默认随机抽 20 个页(innodb_stats_persistent_sample_pages)估算基数。抽样就有误差:数据分布倾斜的列、刚大批量写入还没触发重算的表,估算可能离谱,进而把优化器带沟里。大批量导入之后手动 ANALYZE TABLE,是成本极低的好习惯。

账本之二:成本估算

每个候选执行方案都会折算成一个「成本值」,粗略由两部分组成:

  • IO 成本:预计要读多少页。走二级索引 = 范围内索引页 + 逐行回表的数据页;全表扫 = 表的所有数据页。当范围条件命中行数很多时,回表页数暴涨,索引方案成本反超全表扫——这就是有时走索引有时全扫的原因
  • CPU 成本:读取、比较、排序的行数折算。

想看细账?MySQL 8.0 自带 optimizer trace

SET optimizer_trace = "enabled=on";
SELECT ...;   -- 跑要诊断的 SQL
SELECT * FROM information_schema.OPTIMIZER_TRACE;
SET optimizer_trace = "enabled=off";

trace 输出里每个候选索引都有 rowscost 估算过程,谁被选中、为什么落选,一目了然。

经典翻车:order by limit 选错索引

最难缠的一种:WHERE status = 1 ORDER BY create_time DESC LIMIT 10。两个索引可选:按过滤的 (status),或按排序的 (create_time)。优化器想走排序索引「倒着扫 10 条就停」,免排序很诱人;但 status=1 的行如果集中在历史区域,倒着扫可能扫过几十万行才凑够 10 条 status=1 的。某些版本对 LIMIT 场景的成本估算又偏乐观,于是选了排序索引,SQL 从毫秒劣化到秒级——条件一变(status=2)、数据一涨,行为又变了。

老王这条,就是用 trace 抓到实锤的。

干预手段:从温和到强力

手段说明适用
ANALYZE TABLE重算统计信息,零风险大批量导入后;估算明显失真时
建更合适的索引(status, create_time) 让过滤和排序一树全包首选,治本
改写 SQL拆子查询、调整条件形态优化器理解不了查询意图时
force index强制走指定索引,绕过估算确认估算错误后的最后手段
SELECT * FROM orders FORCE INDEX (idx_status_time)
WHERE status = 1 ORDER BY create_time DESC LIMIT 10;

force index 要慎用、写进代码评审注释里:它把「数据变了」的适应性也锁死了——今天合理的强制,明天数据分布一变可能就是负优化。规范是:先 ANALYZE、再改索引、再改写,都无效且 trace 证实估算错误,才 force。

查询优化这条线到这就齐了:慢日志找 SQL、EXPLAIN 看计划、失效写法排查、成本估算兜底。下一篇切换底层视角——事务的 ACID 不是背出来的口号,redo 和 undo 两个日志才是它真正的骨骼。

咖啡凉了,记得趁热喝。

503

10 年全栈工程师 · 503咖啡馆主理人

#MySQL#优化器#统计信息#force index#optimizer trace

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 5 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞