MySQL优化器如何选择执行计划
MySQL不按SQL字面顺序执行,而是由优化器生成成本最低的执行计划。这个过程不是确定性的——同一SQL在不同数据分布、索引状态或统计信息下,可能生成完全不同的执行计划。
优化器基于
cost_model估算每个候选计划的I/O和CPU开销,核心输入包括:表行数(
INFORMATION_SCHEMA.TABLES.TABLE_ROWS)、索引基数(
SHOW INDEX FROM tbl)、列值分布直方图(MySQL 8.0+)、以及是否启用
optimizer_switch中的各项规则(如
index_merge=on)。 统计信息过期会导致误判:执行
ANALYZE TABLE t强制刷新,而非依赖自动采样
FORCE INDEX能绕过优化器选择,但仅解决表层问题;真正要查的是为什么优化器认为全表扫描更“便宜” 隐式类型转换(如
WHERE varchar_col = 123)会让索引失效,且优化器仍可能选错路径,因为类型转换发生在执行阶段,优化器无法准确预估过滤效果
EXPLAIN输出里哪些字段最关键
EXPLAIN不是执行日志,而是优化器“打算怎么做”的快照。重点关注
type、
key、
rows、
Extra四列,它们直接暴露策略缺陷:
type为
ALL或
index:大概率缺失有效索引,或查询条件未覆盖索引最左前缀
key为
NULL:优化器明确放弃使用索引,常见于
OR条件未被索引覆盖、函数包裹字段(如
WHERE YEAR(create_time) = 2023)
rows远大于实际结果集:说明索引选择不当或统计不准,
rows是优化器预估的扫描行数,不是返回行数
Extra含
Using filesort或
Using temporary:排序或分组未走索引,需检查
ORDER BY/
GROUP BY字段是否在索引中连续出现
为什么FORCE INDEX有时反而更慢
强制指定索引会跳过成本估算,但不改变底层执行逻辑。如果强制的索引无法覆盖查询所需字段(即非覆盖索引),MySQL仍需回表读取完整行,而优化器原本选的全表扫描可能因顺序I/O更快。
复合索引(a,b,c)上执行
SELECT * FROM t WHERE a=1 ORDER BY b,即使
FORCE INDEX(a,b),
c字段仍需回表;若表不大,全表扫描+内存排序反而更优
FORCE INDEX不阻止
Using filesort:它只影响访问路径,不影响排序方式 用
EXPLAIN FORMAT=JSON查看
used_key_parts和
used_columns,确认索引是否真正被“用满”
统计信息不准时怎么快速验证
优化器严重依赖统计信息,但
ANALYZE TABLE默认采样率低(
innodb_stats_persistent_sample_pages=20),大表容易误判。 临时提高采样精度:
SET SESSION innodb_stats_persistent_sample_pages = 100,再执行
ANALYZE TABLE t检查实际基数是否失真:
SELECT COUNT(DISTINCT col) FROM tvs
SHOW INDEX FROM t里的
Cardinality值,偏差超5倍就该怀疑 禁用持久化统计(
innodb_stats_persistent=OFF)可让每次重启后重新采样,适合测试环境快速验证,但生产慎用
执行计划不是配置出来的,是数据、索引、统计信息和查询结构共同作用的结果。改SQL、加索引、刷统计,三者常需同步调整,单点改动往往无效。
