优化器选错索引主因是统计信息失真、索引不可用写法或复合索引设计不当;应优先校验CARDINALITY与实际唯一值偏差,修正WHERE写法,优化复合索引顺序,并用EXPLAIN FORMAT=JSON深入分析。

EXPLAIN 的 rows 和实际扫描行数差一个数量级,先查统计信息准不准
优化器不看真实数据分布,只信 information_schema.STATISTICS 里存的 CARDINALITY 和采样估算值。比如 SHOW INDEX FROM orders 显示 idx_status 的 CARDINALITY 是 5,但实际该字段只有 3 个值('pending'/'done'/'failed'),说明采样严重失真——优化器就以为这个索引区分度高,可能错误优先选它。
实操建议:
- 用
SELECT TABLE_NAME, COLUMN_NAME, CARDINALITY FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = 'your_db' AND TABLE_NAME = 'your_table'对比字段实际唯一值数量(COUNT(DISTINCT col)) - 若偏差 > 3 倍,大概率要
ANALYZE TABLE your_table;但别在 MySQL 5.7 及以前版本高峰期跑,会锁表 - MySQL 8.0+ 可设
innodb_stats_persistent_sample_pages = 128(默认 20),提高采样精度,尤其对大表有效
WHERE 条件含函数、隐式转换或 OR,索引直接被“判死刑”
不是优化器“选错”,而是某些写法让索引根本不可用,优化器只能从剩下选项里挑——哪怕只剩全表扫描。
常见错误现象:
-
WHERE DATE(created_at) = '2024-01-01'→created_at索引失效,改用created_at BETWEEN '2024-01-01' AND '2024-01-01 23:59:59' -
WHERE user_id = 123但user_id是VARCHAR→ 触发隐式转换,改写为WHERE user_id = '123' -
WHERE a = 1 OR b = 2→ 大概率放弃所有单列索引,除非有覆盖(a,b)的复合索引且能走 index merge(但成本高,常被跳过)
复合索引顺序错、ORDER BY 不在索引中,优化器被迫“半放弃”
即使 WHERE 匹配了索引前缀,如果后续要排序或分页的字段不在索引里,优化器就得在索引扫描后额外 filesort——它会权衡:是走索引过滤 + 排序,还是直接全表扫描顺便排?后者有时反而更省。
例如查询 SELECT * FROM t WHERE status = 'done' ORDER BY created_at LIMIT 10:
- 有索引
idx_status (status)→ 只能过滤,ORDER BY没着落,大概率Using filesort - 有索引
idx_status_created (status, created_at)→ 过滤 + 天然有序,LIMIT 10可提前终止,rows通常低得多 - 若还查
SELECT *,仍要回表;改成SELECT status, created_at, id+ 覆盖索引,彻底避免回表
FORCE INDEX 是止痛药,不是诊断书
加了 FORCE INDEX (idx_x) 后变快,不代表原来就“选错”——可能只是掩盖了更深层问题:比如数据倾斜(80% 的 status = 'pending')、buffer pool 命中率突降、或 sort_buffer_size 不足导致 filesort 成本飙升但优化器按默认值估算。
真正该做的:
- 用
EXPLAIN FORMAT=JSON看used_range_access和rows_examined_per_scan,确认强制后是否真减少扫描量 - 检查
filtered字段:低于 10 表示条件选择性差,索引天然不适合,硬推没意义 - 禁用
FORCE后跑SELECT COUNT(*)验证实际命中行数,和EXPLAIN的rows对齐——不齐就说明统计信息是根因,不是 SQL 写法
复杂点在于:统计信息不准、数据分布突变、和优化器静态成本模型三者常同时发生。你看到的“选错”,往往是其中一环崩了,但日志里只显示最终执行计划,不会告诉你哪一步算歪了。


















