优化器未走索引大概率因统计信息过时;需用ANALYZE TABLE更新cardinality,注意锁和触发条件,并在ETL后、慢查询突增时优先排查。

为什么 EXPLAIN 显示索引存在却没走?
不是索引写错了,也不是优化器“失灵”,大概率是优化器基于的统计信息过时了。MySQL(尤其是 InnoDB)依赖 INFORMATION_SCHEMA.STATISTICS 和内存中的采样数据估算索引选择性,如果表刚批量导入、大量 DELETE 或 UPDATE 后没触发自动更新,统计值就严重偏离真实分布——比如一个本该高选择性的字段,统计显示重复值极多,优化器就会直接放弃走索引,改用全表扫描。
ANALYZE TABLE 能立刻修复统计偏差吗?
能,但要注意触发条件和副作用:
-
ANALYZE TABLE会重新采样索引页,更新cardinality值,对大多数场景足够有效; - 它会加表级读锁(InnoDB 是轻量级的 MDL 锁),大表执行时可能阻塞 DDL,但一般不影响 DML;
- MySQL 8.0+ 默认开启
innodb_stats_auto_recalc=ON,但只在表变更行数超 10% 且至少 1000 行时才触发,小表或低频更新表容易漏掉; - 手动执行后,建议立刻用
SHOW INDEX FROM table_name查看Cardinality列是否明显变化,别只信EXPLAIN结果。
什么时候该怀疑统计信息而不是 SQL 写法?
出现以下现象时,优先查统计而非重写 SQL:
- 同一条查询,在测试库走索引、生产库走全表扫描(数据量/结构一致,但生产库近期有大批量写入);
-
WHERE a = ? AND b = ?中,a是前导列的联合索引,但EXPLAIN显示key为NULL; - 执行
OPTIMIZE TABLE后索引突然被选中(本质是OPTIMIZE内部会调用ANALYZE); -
SELECT COUNT(*)结果和SHOW TABLE STATUS的Rows相差一个数量级——这是统计失准最直观的信号。
还有哪些操作会意外污染统计信息?
除了常规增删改,这些行为会让统计更不可靠:
- 用
mysqldump --single-transaction导出再导入,新表的统计信息是空的或默认初始值(如Cardinality=1),不是从原表继承; - 分区表中只
TRUNCATE PARTITION某个分区,其余分区统计不更新,优化器仍按旧数据量估算; - 设置了
innodb_stats_persistent=OFF(5.6 默认),重启 MySQL 后统计全部丢失,首次查询前全是预估垃圾值; - 使用
LOAD DATA INFILE导入百万级以上数据后没手动ANALYZE,优化器看到的还是导入前的稀疏分布。
统计信息不是“设一次就完事”的配置,它是动态快照。只要表数据分布发生显著偏移,就得主动刷新——尤其在 ETL 任务后、上线前、慢查询突增时,ANALYZE TABLE 应该是第一排查动作,而不是最后才想起来。


















