优化器选了索引但执行慢,本质是选错索引而非未走索引;主因包括统计信息不准致基数误判、ORDER BY/LIMIT 诱导选择低效索引、隐式转换或 OR 条件迫使全表扫描。

EXPLAIN 显示用了索引,但实际执行很慢
这说明优化器确实选了某个索引(key 字段非 NULL),但该索引不是最优解。常见现象是 type 为 range 或 ref,rows 却高达几十万——扫描行数远超预期。根本原因不是“没走索引”,而是“走了错的索引”。比如 WHERE a BETWEEN 10000 AND 20000 本该走索引 a,却因统计信息过期,优化器误判走主键索引更省事,结果全表扫描。
MySQL 统计信息不准导致基数误判
优化器依赖 INFORMATION_SCHEMA.STATISTICS 和采样估算的“基数”(cardinality)来预估扫描成本。但 InnoDB 默认只采样 8 个数据页,当表数据批量变更(如删完重插)、或分布严重倾斜(如 90% 的 status='active')时,基数会严重失真。
解析 MySQL 8.0 的 DDL 脚本,并将表结构导出为 Excel 格式。适用于用户需将 MySQL 的 `CREATE TABLE` 语句转换为结构化的 Excel 文件场景,导出内容包含字段名、数据类型、长度及精度等信息。支持多张表的批量处理,每张表对应 Excel 中的一个独立工作表。适用于数据库文档编写、数据字典生成及模式分析等任务。
- 查当前统计:运行
SHOW INDEX FROM t,关注Cardinality列是否明显偏离真实值 - 强制更新:执行
ANALYZE TABLE t,触发重新采样(注意:该操作会加 MDL 读锁,大表慎用) - 长期方案:对高频变更的大表,可配置
innodb_stats_persistent = ON+ 定期ANALYZE,避免依赖易失效的内存采样
ORDER BY 或 LIMIT 诱导优化器放弃高效索引
当查询含 ORDER BY b LIMIT 1,而 b 有索引、a 条件选择性更高时,优化器可能优先选 b 索引——只为省掉排序步骤。但它低估了回表代价:用 b 索引找到第一条满足 b 条件的记录后,仍需回主键查整行,再判断是否满足 a 条件;若不满足,就得继续往下找,直到匹配为止。结果扫描行数爆炸。
- 验证方式:对比
EXPLAIN中rows和实际SELECT COUNT(*)结果,差距大就说明预估失准 - 临时解法:加
FORCE INDEX(a)强制走条件列索引,但要同步补上ORDER BY b对应的覆盖字段(如建联合索引(a, b)) - 根本解法:建覆盖索引,例如
KEY idx_a_b (a, b),让WHERE a = ? ORDER BY b LIMIT 1一步到位
隐式转换或 OR 条件让优化器“放弃治疗”
这类写法不会直接让优化器“选错”索引,而是让它干脆放弃使用任何索引——转为全表扫描。比如 WHERE user_id = 12345(user_id 是 VARCHAR)触发隐式转换,或 WHERE a = ? OR b = ?(a、b 各有单列索引)让优化器无法合并扫描路径。
- 检查
EXPLAIN的type:若为ALL,且possible_keys非空,基本锁定是类型不匹配或OR导致 - 修复
OR:拆成UNION ALL,确保每个分支能独立走索引;注意用ALL而非UNION,避免去重开销 - 修复隐式转换:统一参数类型,字符串条件必须加引号,数值字段查询传数字——别依赖数据库自动转
EXPLAIN 看着合理、rows 预估也不高,但实际执行就是卡住的 case。这时候得盯住 Handler_read_* 状态变量和 profiling 输出,看真实 IO 次数是否远超预估——优化器的“成本模型”和你的磁盘、缓冲池现状,可能根本不在同一套假设里。

















