MySQL优化器在多索引场景下基于成本模型选择最优索引,仅考虑满足最左前缀原则且可下推的索引,综合估算I/O、回表、过滤、排序等代价后选取总成本最低路径。

MySQL 优化器在多索引场景下,并不简单“选最匹配的索引”,而是对每个可用索引(包括单列、复合、覆盖等)分别估算执行成本,再挑总成本最低的那个路径。它本质上是在做一次带约束的局部穷举:不是所有索引都参与比较——只有能用于当前WHERE/ON条件、且满足索引最左前缀规则的索引才会被纳入候选集。
哪些索引会被考虑?
优化器只评估真正可下推到存储引擎层的索引:
- 必须满足最左前缀原则:比如有索引 (a, b, c),查询条件含 WHERE a = ? AND b > ? 可用;但仅 WHERE b = ? 就不可用
- 范围条件之后的列不再用于索引查找:如 WHERE a = 1 AND b > 5 AND c = 10,索引 (a,b,c) 中 c 不参与范围扫描,但可能用于ICP(Index Condition Pushdown)过滤
- 隐式类型转换或函数包裹会直接让索引失效,这类索引不会进入成本计算流程
成本怎么算?核心是 I/O + CPU 的加权预估
优化器把一次索引访问拆解为几个可量化的代价项:
-
读取索引页的成本:基于
engine_cost表中的io_block_read_cost(默认约1.0),乘以预估需读取的索引页数 -
回表成本(如果需要):若索引不覆盖查询列(如 SELECT * 但只有
(a)索引),每行匹配后还要根据主键查聚簇索引,按row_evaluate_cost+ 额外IO计费 -
条件过滤成本:对索引扫描出的每一行,执行WHERE中剩余条件的CPU开销,受
filtered值影响(EXPLAIN 中该值越接近100,说明过滤越高效) -
排序/临时表附加成本:若索引不能满足ORDER BY或GROUP BY,还要叠加
sort_cost或内存/磁盘临时表开销
为什么有时没选“看起来更优”的索引?
常见原因不是优化器错了,而是成本模型依赖的输入不准或权重偏移:
-
统计信息过期:
ANALYZE TABLE没及时运行,导致rows和filtered严重失真,比如实际只有100行满足条件,优化器却以为有10万行 -
复合索引列顺序不合理:比如高频查询是
WHERE status = ? AND create_time > ?,但索引建成了(create_time, status),优化器发现create_time范围大、选择性差,整体成本反而高于全表扫描 -
覆盖索引被低估:当
SELECT a,b完全命中(a,b,c)索引时,Using index提示出现,此时省去回表,I/O大幅下降——但若memory_block_read_cost被调得过高,优化器可能仍倾向走小一点但要回表的索引
怎么引导优化器选对索引?
不靠猜,靠控制输入和验证输出:
- 对频繁查询的表定期执行
ANALYZE TABLE t,确保基数、分布等统计值新鲜 - 用
EXPLAIN FORMAT=JSON查看每个候选索引的详细成本分解(cost_info字段),比普通EXPLAIN更直观 - 手动测试关键索引:用
FORCE INDEX强制走某索引,对比执行时间与EXPLAIN预估成本是否趋势一致 - 调整成本常量要谨慎:比如调低
row_evaluate_cost可能让优化器更倾向使用过滤率低但索引扫描快的路径,适合CPU强、IO弱的环境


















