MySQL优化器通过总成本=I/O成本+CPU成本公式精确计算,当索引路径的随机IO回表开销叠加键比较、行评估等成本超过全表扫描的顺序IO时,即判定全表扫描更优。

MySQL优化器怎么算出「全表扫描比走索引便宜」
它不是拍脑袋决定的,而是真刀真枪地代入公式算出来的。核心逻辑就一条:总成本 = I/O 成本 + CPU 成本,所有执行路径(全表扫、索引扫、索引+回表、临时表排序等)都按这个公式逐项估算,最后比大小。
比如一条 SELECT * FROM orders WHERE status = 'shipped',优化器会分别计算:
- 全表扫描:读取全部
Rows行,每行评估成本是row_evaluate_cost(默认 0.1),I/O 按页数估算,依赖Data_length / 16KB和innodb_page_size - 走
status索引:先查索引页(I/O 成本含io_block_read_cost),再根据Cardinality估算匹配行数,接着回表读聚簇索引(又是一轮 I/O),每行还要做键比较(key_compare_cost)和回表后行评估
如果 status 区分度极低(比如 95% 都是 'shipped'),那索引扫描页数接近全表,回表开销又叠加,总成本很容易超过全表扫描——这时候不走索引,就是算出来的理性选择,不是 bug。
server_cost 和 engine_cost 表里改的是什么
这两张表存的是成本常量,不是统计信息,也不是阈值开关。它们定义了每一类操作「在 MySQL 成本单位体系下值多少钱」:
-
server_cost控制 Server 层行为:比如key_compare_cost(默认 0.05)代表一次索引键比较花 0.05 单位;row_evaluate_cost(默认 0.1)代表对一行做 WHERE 条件判断花 0.1 单位 -
engine_cost控制引擎层行为:比如io_block_read_cost(默认 1.0)代表一次随机磁盘页读取定义为「基准单位 1.0」;memory_block_read_cost(默认 1.0)代表内存页读取成本,通常设得更低
你改这些值,等于在调优器的「计价表」上动笔——比如把 io_block_read_cost 从 1.0 改成 2.0,优化器就会认为磁盘 IO 更贵,更倾向避免扫描、多用索引或内存临时表。但别乱调:生产环境没压测支撑的修改,可能让原本合理的计划突然翻车。
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
为什么 ANALYZE TABLE 后执行计划变了
因为优化器的成本计算严重依赖统计信息,而 ANALYZE TABLE 是刷新这些信息的唯一可靠方式。关键字段包括:
-
Cardinality:决定索引选择性的核心。比如联合索引(a,b)的Cardinality若远低于表总行数,说明a列重复率高,优化器会倾向放弃该索引 -
Rows(SHOW TABLE STATUS输出):影响全表扫描成本基线。若该值长期不准(比如大表只增不 ANALYZE),优化器会低估扫描代价 -
InnoDB自动采样受innodb_stats_persistent和innodb_stats_sample_pages控制,默认仅采样 8 页,数据倾斜时极易失真
常见陷阱:用 SELECT COUNT(*) 验证行数 ≠ 优化器看到的 Rows 值;或者以为「建了索引就一定生效」,却没检查对应索引的 Cardinality 是否被正确采集。
哪些地方最容易被当成「优化器失灵」,其实是成本算对了
很多所谓「索引失效」,其实是 CBO 在当前统计信息和成本参数下,诚实算出了更优解。典型场景有:
- 小表(
Rows ):全表扫描的 I/O 成本可能远低于走索引再回表的两次随机读,尤其当innodb_buffer_pool_size足够大、数据全在内存时 - 范围查询返回大量行(如
WHERE create_time > '2020-01-01'):索引定位快,但后续回表行数太多,CPU 成本(row_evaluate_cost × N)飙升 - 使用了函数或隐式转换(如
WHERE DATE(create_time) = '2026-11-01'):直接导致该索引无法进入候选列表,根本不会参与成本计算
真正要怀疑优化器之前,先确认三件事:EXPLAIN 输出的 rows 预估是否接近真实扫描行数、对应索引的 Cardinality 是否合理、server_cost 和 engine_cost 是否被意外覆盖。漏掉任意一项,调优就变成蒙眼扔飞镖。










