优化器选错索引源于统计信息过时、索引不可用或查询写法不当;需先验证索引存在性与可用性,再更新统计信息并修正SQL写法以确保索引生效。

优化器选错索引不是“随机失误”,而是它基于过时或失真的统计信息、被破坏的索引可用性、或不匹配的查询写法,做出了看似合理实则低效的决策。直接加 FORCE INDEX 是堵漏,不是防漏;真正避免选错,得从它依赖的输入和执行路径上动手。
确认是不是真选错了,还是索引根本不可用
很多“选错”其实是假象:优化器压根没考虑你期望的索引,因为它在当前查询下根本不能用。
-
EXPLAIN显示key为NULL或走其他索引?先查SHOW INDEX FROM table_name,确认目标索引存在且是 B-tree 类型(比如PRIMARY、idx_status_created) - WHERE 条件是否满足最左前缀?比如索引是
(a, b, c),但查询只写了WHERE b = 1 AND c = 2—— 这个索引无法命中,FORCE INDEX也救不了 - 有没有对索引字段做函数操作?
WHERE DATE(created_at) = '2024-01-01'或WHERE UPPER(name) = 'ABC'会让索引失效,统计再准也没用 - 是否存在隐式类型转换?
user_id是INT,但传参是字符串'123',MySQL 会在列上加转换函数,索引直接出局
让优化器“看见”真实数据分布
统计信息不准是选错索引最常见根源。优化器靠 CARDINALITY 和采样行数估算代价,不是靠猜。
- 执行
ANALYZE TABLE table_name—— 它会重新随机采样索引页,刷新information_schema.STATISTICS中的基数和行数预估 - 检查
innodb_stats_persistent是否为ON:若为OFF,统计只存内存,重启或元数据操作后就丢,ANALYZE效果不持久 - 大表或数据倾斜严重时,默认采样页数(约 10–20 页)可能不够准,可临时调高:
SET GLOBAL innodb_stats_persistent_sample_pages = 100 - 执行完
ANALYZE TABLE后,务必查information_schema.STATISTICS确认CARDINALITY值已更新,而不是只看时间戳
写法上守住索引生效的底线
再好的索引,写法一错就白搭。这些细节不改,优化器永远没得选。
- 范围查询别用函数:把
WHERE DATE(create_time) = '2024-01-01'改成WHERE create_time >= '2024-01-01' AND create_time - LIKE 模糊查慎用左通配:
LIKE '%keyword'必然全表扫;能用右模糊('keyword%')就用,否则考虑FULLTEXT或外部搜索引擎 - OR 条件要小心:如果
WHERE a = 1 OR b = 2,而只有a有索引、b没索引,整个条件大概率触发全表扫描;拆成UNION或补索引更稳妥 - 避免
!=、IS NULL等低选择性操作符直接作用于索引列,尤其当该列重复值多时,优化器宁可扫表
线上强制干预前必须验证的三件事
FORCE INDEX 不是开关,是手术刀——切得准才救命,切歪了反而加速死亡。
- 对比
EXPLAIN:原 SQL 和加提示后的key、rows、Extra(特别注意是否出现Using filesort或Using temporary) - 压测不能只跑一次:在准生产环境用全量时间范围或高频参数组合压测,观察
Query_time、Rows_examined、CPU 和 I/O 消耗 —— 小数据快 ≠ 大数据稳 - 必须设熔断:应用层加 SQL 执行超时(如 MyBatis 的
timeout),DBA 侧配置慢日志告警(例如执行时间超过原基准 150% 即触发)
真正容易被忽略的是:哪怕 ANALYZE TABLE 成功、统计更新了,优化器也可能沿用旧的执行计划缓存。有时需要 FLUSH TABLES 或新建数据库连接才能触发重编译——不验证实际执行效果,光看 EXPLAIN 输出可能误判。


















