mysql执行器通过优化器将like 'abc%'重写为name >= 'abc' and name
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、INDEX(name, id, email)。真正卡住性能的往往不是“会不会用 LIKE”,而是没验证
ORDER BY name可利用该索引排序,但ORDER BY email就不行- 索引字段越多,写入越慢、内存占用越高,别把不常查的字段硬塞进去
- 如果
name列本身很长(比如 TEXT),覆盖索引可能膨胀严重,得权衡EXPLAIN输出是否真的type=range且rows合理——很多线上慢查看着像走了索引,实则是伪range,rows接近全表。












