MySQL 8.0优化器因成本估算更严格而主动弃用索引,EXPLAIN FORMAT=JSON中出现"chosen": false,"cause": "cost"即为证据;函数如DATE()加重代价惩罚,应改用范围查询替代。

不是索引“坏了”,是MySQL 8.0的优化器更较真了——它算出来走索引更贵,就直接弃用了。
EXPLAIN里看到"chosen": false和"cause": "cost"
这是最直接的证据。升级后执行EXPLAIN FORMAT=JSON,在considered_plan里如果出现"chosen": false, "cause": "cost",说明优化器明确评估过该索引路径,但因预估成本过高而主动放弃。这不是找不到索引,是“看不上”。
- 老版本(如5.7)可能容忍高成本路径,8.0默认更激进地倾向顺序I/O
-
rows_for_plan数值大(比如24万+)、cost_for_plan远高于全表扫描预估成本,就是典型信号 - 别只看
key是否为空——要翻JSON输出里的best_access_path段
WHERE里用DATE()、YEAR()这类函数在8.0代价惩罚加重
函数本身让索引失效的老问题没变,但8.0对这类操作的代价估算更严格。哪怕你只查一天数据,WHERE DATE(create_time) = '2026-09-01'也会被标记为高成本,因为优化器知道:必须对每行计算一次函数再比对。
解析 MySQL 8.0 的 DDL 脚本,并将表结构导出为 Excel 格式。适用于用户需将 MySQL 的 `CREATE TABLE` 语句转换为结构化的 Excel 文件场景,导出内容包含字段名、数据类型、长度及精度等信息。支持多张表的批量处理,每张表对应 Excel 中的一个独立工作表。适用于数据库文档编写、数据字典生成及模式分析等任务。
- ✅ 正确改写:用范围代替函数,
create_time >= '2026-09-01' AND create_time - ⚠️ 注意:如果字段是
DATETIME,别漏掉时间部分,否则可能漏数据或扩大范围 - ❌ 即使加了函数索引(如
INDEX ( (DATE(create_time)) )),8.0也未必选它——要看实际数据分布和统计信息是否更新
IN子查询无索引时,外层索引直接被连坐
像WHERE status IN (SELECT status FROM config WHERE active = 1)这种写法,在5.7可能侥幸走外层status索引;8.0会把整个子查询当作一个黑盒,一旦子查询没走索引、返回结果多,优化器就认为“外层索引过滤性不可信”,干脆放弃。
- ✅ 拆成JOIN:先查出
config中有效的status列表,再和主表JOIN - ✅ 或确保子查询有高效索引:
config(active, status)联合索引 - ⚠️
EXPLAIN里若看到select_type为DEPENDENT SUBQUERY且type是ALL,基本就是这个原因
ORDER BY + LIMIT配合联合索引时,过滤性差的前导列让优化器“心凉”
比如有索引INDEX(user_id, create_time),但user_id值高度重复(如90%都是1),这时WHERE user_id = 1 ORDER BY create_time DESC LIMIT 20在8.0容易被判定为“需回表取大量行再排序”,不如全表扫描+filesort来得快。
- ✅ 加强过滤:在
WHERE中补充其他高区分度条件,比如AND is_deleted = 0 - ✅ 调整索引顺序:如果
create_time本身区分度高,可考虑INDEX(create_time, user_id)(注意业务语义是否允许) - ⚠️
key_len显示只用了索引前1个字节,但rows却很高,就是前导列过滤性差的明证
真正卡住人的地方,往往不是SQL写错了,而是优化器基于新版本统计信息和成本模型,做出了和旧版不同的判断。不看FORMAT=JSON输出、不验证数据分布、只靠“经验口诀”去改SQL,很容易绕弯路。

















