mysql优化器选错索引不是bug,而是cbo在统计信息不准、采样偏差或成本模型静态化时的合理误判;本质是预估成本与真实i/o、cpu、缓存命中率等现实因素的偏差。

MySQL优化器选错索引不是故障,而是它在信息不完备时做出的“合理误判”——它算出来的最低成本路径,和你看到的实际执行慢,本质是估算与现实的脱节。
EXPLAIN 的 rows 为什么总不准
因为 rows 是估算值,不是真实扫描数。InnoDB 默认只随机采样 innodb_stats_persistent_sample_pages 个叶子页(持久化开启时默认 20 页),再插值推算整棵树的基数。数据倾斜严重时(比如 status 字段 95% 是 'pending'),采样可能完全漏掉稀有值,导致 CARDINALITY 低估一个数量级。
-
SHOW INDEX FROM orders查出的CARDINALITY和实际唯一值差 10 倍以上,rows基本不可信 - 即使刚跑完
ANALYZE TABLE,紧接着大批量写入,统计立刻滞后 - 采样机制本身不支持动态权重,无法反映 buffer pool 热度、SSD 延迟、当前系统负载等真实因素
FORCE INDEX 上线就炸的三个原因
硬指定索引把“估算风险”转成“强依赖风险”,一旦条件变化,SQL 直接失败。
- 索引被
DROP INDEX或重命名后,查询报错ERROR 1176 (HY000): Key 'idx_user_status' doesn't exist in table 'orders',不会降级、不兜底 - 复合索引前导列没出现在
WHERE条件里(如索引是(a,b),查询只用b),FORCE INDEX无效 - 数据分布突变后(如
user_id突然涌入大量测试账号),原高效索引变成低效范围扫描,但强制指令照常执行
真正容易被忽略的缓存陷阱
哪怕 ANALYZE TABLE 成功刷新了统计信息,优化器仍可能沿用旧执行计划——因为计划缓存没失效。
- MySQL 不会自动触发计划重编译,尤其在连接复用场景下(如连接池),旧计划可能持续数小时甚至更久
- 需要手动触发:执行
FLUSH TABLES(影响大),或断开重建连接(更可控) - 某些 ORM(如 MyBatis)对同一 SQL 模板缓存执行计划,即使参数不同,也可能复用错误计划











