where条件对索引列使用函数(如year(create_time)=2024)会导致索引失效,因优化器无法将函数结果与b+树中原始值有序比较;应重写为裸列比较,如create_time>='2024-01-01' and create_time

为什么 WHERE 条件加函数会让索引失效
直接对索引列用函数(比如 WHERE YEAR(create_time) = 2024)会导致 MySQL 无法使用该列上的索引,因为优化器无法将函数结果与 B+ 树中存储的原始值做有序比较。即使 create_time 上有索引,这条语句也会触发全表扫描。
真正要重写 SQL 的核心思路是:把函数从列上“挪走”,改到右侧常量侧,让左侧保持裸列引用。
-
WHERE DATE(create_time) = '2024-01-01'→ 改成WHERE create_time >= '2024-01-01' AND create_time -
WHERE UPPER(name) = 'ABC'→ 改成WHERE name = 'abc'(前提是字段已建COLLATE utf8mb4_general_ci或类似大小写不敏感排序规则) -
WHERE id + 1 = 100→ 改成WHERE id = 99
隐式类型转换怎么悄悄干掉索引
当 WHERE 中列和值类型不一致时,MySQL 可能自动做类型转换,而转换逻辑往往施加在列上——这就等价于给列套了函数。典型场景是字符串字段存数字(如 user_id VARCHAR(32)),却写成 WHERE user_id = 123。
执行 EXPLAIN 会看到 type: ALL 或 key: NULL,但你可能根本没意识到发生了转换。
- 检查方式:运行
SHOW WARNINGS,看是否有Implicit type conversion提示 - 修复方式:统一类型,比如改成
WHERE user_id = '123'(确保引号包裹) - 更彻底的做法:把字段类型改为
BIGINT,并加NOT NULL,避免后续误用
LIKE 左模糊为什么不能走索引,以及怎么绕过
WHERE name LIKE '%abc' 无法使用索引,因为 B+ 树只支持最左前缀匹配;而 WHERE name LIKE 'abc%' 是可以的。
如果业务真需要左模糊(比如搜索关键词包含某子串),又不想全表扫,可考虑:
- 用全文索引(
FULLTEXT)配合MATCH ... AGAINST,适合中文需搭配ngram插件 - 倒排索引思路:额外建一个
substrings表,预存所有长度 ≥ 3 的子串 + 原始 ID,查询时走这个表的等值索引 - 放弃 SQL 层解决,改用 Elasticsearch 或 SQLite FTS 等专用方案
注意:不要用 WHERE name LIKE '%abc%' 加 FORCE INDEX,它不会生效,优化器会直接忽略提示。
ORDER BY 和 LIMIT 组合导致索引失效的重写陷阱
常见误区是以为只要 ORDER BY 列有索引就一定快,但若 WHERE 条件过滤后结果集太大,MySQL 可能放弃索引排序,改用 filesort —— 尤其当 SELECT * 拿回大量字段时。
重写关键点不是“加索引”,而是减少排序开销:
- 确认是否真的需要全部字段:
SELECT id, name比SELECT *更容易命中覆盖索引 - 把
WHERE条件尽可能收紧,比如加时间范围限制,缩小排序数据量 - 如果分页深度大(如
LIMIT 10000, 20),改用游标分页:WHERE created_at
覆盖索引(covering index)在这里很关键:如果 SELECT 字段、WHERE 字段、ORDER BY 字段都能被单个索引包含,MySQL 就不必回表,性能提升明显。
索引失效往往不是语法错,而是语义错——你写的 SQL 在逻辑上就无法利用现有索引结构。重写时别只盯着“怎么加 hint”,先问一句:我是不是在拿 B+ 树当哈希表用?











