MySQL执行器通过优化器将LIKE 'abc%'重写为name >= 'abc' AND name < 'abd',从而利用B+树索引的范围查询能力实现高效匹配。

LIKE 'abc%' 是怎么被 MySQL 执行器“看懂”的
MySQL 执行器不直接解析 LIKE 语义,而是依赖优化器把 name LIKE 'abc%' 重写成等价的范围条件:name >= 'abc' AND name 。这个转换成立的前提是字段使用 B+ 树索引、排序规则支持字典序比较(绝大多数 utf8mb4 collation 都满足),且无隐式类型转换。
执行器拿到这个范围条件后,就按标准 B+ 树 range scan 流程走:定位到第一个 ≥'abc' 的索引项,然后顺序向右遍历,直到遇到 ≥'abd' 的键为止。整个过程不回表、不逐行匹配,纯索引页内扫描。
-
EXPLAIN中type显示为range,key显示索引名,rows值远小于总行数,才是真走索引 - 如果字段是
VARCHAR(255)但实际值普遍很短,建前缀索引如INDEX(name(12))可减小索引体积,但必须确保'abc%'的前缀长度 ≤ 12 - 联合索引下,只有最左字段参与前缀匹配才生效,
INDEX(status, name)对WHERE name LIKE 'abc%'完全无效
为什么 UPPER(name) LIKE 'ABC%' 会失效
函数包裹让索引键无法直接比对。执行器看到的是 UPPER(name) 这个表达式,不是原始 name 值,B+ 树里存的仍是未转大写的原始字符串,两者无法对齐。
即使你手动把参数转成大写,比如 UPPER(name) LIKE UPPER('abc') + '%',优化器也无法在预编译阶段推导出固定前缀,静态分析失败,索引跳过。
- 补救办法是建函数索引:
CREATE INDEX idx_name_upper ON t1 ((UPPER(name)))(MySQL 8.0.13+) - 旧版本只能冗余一列
upper_name并建普通索引,应用层同步维护 -
COLLATE不同也会触发隐式转换,例如字段是utf8mb4_0900_as_cs,而查询用'abc%' COLLATE utf8mb4_general_ci,索引同样失效
参数化查询中 CONCAT('%', ?) 为何不走索引
预编译阶段,MySQL 无法确定 ? 绑定的值是否构成固定前缀。哪怕你传入 'abc',执行器看到的仍是 CONCAT('%', ?) 这个动态表达式,优化器无法做字面量推导,自然放弃索引。
这和手写 WHERE name LIKE '%abc' 的本质不同——后者是静态 SQL,优化器可提前判断模式结构;前者是运行时拼接,安全边界更严。
- 正确做法是应用层拼好前缀再传参:
WHERE name LIKE ?,绑定参数为'abc%' - 绝对不要在 SQL 里用
CONCAT或+拼接模糊模式 - ORM 框架如 MyBatis 的
#{}默认安全,但${}直接拼字符串,容易误写出LIKE '${prefix}%'这类危险写法
覆盖索引如何避免回表放大 I/O
就算 name LIKE 'abc%' 走了索引,SELECT * 仍要回聚簇索引捞整行数据。覆盖索引把所有需要字段塞进二级索引,执行器直接从索引页返回结果,省掉回表开销。
关键点在于字段顺序:name 必须放最左,后面跟 SELECT 和 ORDER BY 用到的字段。例如常查 id、name、email,就建 INDEX(name, id, email)。
-
ORDER BY name可利用该索引排序,但ORDER BY email就不行 - 索引字段越多,写入越慢、内存占用越高,别把不常查的字段硬塞进去
- 如果
name列本身很长(比如 TEXT),覆盖索引可能膨胀严重,得权衡
EXPLAIN 输出是否真的 type=range 且 rows 合理——很多线上慢查看着像走了索引,实则是伪 range,rows 接近全表。


















