MySQL选错索引主因是优化器依赖失真或过时的统计信息(如CARDINALITY偏差超3倍),而非SQL或索引本身错误;应通过ANALYZE TABLE更新统计,避免函数/隐式转换导致索引失效,并慎用FORCE INDEX。

MySQL 选错索引,绝大多数时候不是 SQL 写错了、也不是索引建得不对,而是优化器“算错了”——它信了过时或失真的统计信息,误判了成本。
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 及以前版本高峰期别跑
ANALYZE TABLE,会锁表;8.0+ 可调innodb_stats_persistent_sample_pages = 128提高采样精度
WHERE 里用了函数、隐式转换或 OR?索引根本没被考虑
这不是优化器“选错”,是某些写法让索引直接不可用,优化器只能从剩下选项里挑——哪怕只剩全表扫描。
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
-
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 能止血,但掩盖了真正的问题
SELECT * FROM orders FORCE INDEX (idx_user_status) WHERE user_id = 123; 立刻生效,但它绕过了优化器决策,也埋下了隐患:
- 硬编码索引名:后续删索引或改名会直接报错
ERROR 1176 (HY000): Key 'xxx' doesn't exist in table 't' - 丧失自适应能力:一旦某
user_id数据量暴涨十倍,强制走索引反而更慢 - 只解决单条 SQL,其他查询依然受错误统计影响——问题只是被局部压制
统计信息不准这件事本身没有日志、不报错、不告警,它安静地拖垮性能,直到慢查询像雪崩一样涌进来。最危险的是你以为索引还在工作,其实优化器早已悄悄弃用。

















