在MySQL的使用过程中,Like模糊查询是非常常见的需求,但很多同学会发现,同样的字段加了索引,有时查询飞快,有时却变成全表扫描。核心原因并不在索引本身,而在于MySQL解析器对Like模式中通配符位置的判定。当优化器在生成执行计划时,会依据解析器产出的语法树信息,判断这个Like条件能否利用B+树索引的有序特性。

解析器如何识别Like模式中的通配符位置
MySQL接收到一条带有Like的SQL后,首先由词法分析和语法分析器将其转化为抽象语法树。对于形如 column LIKE 'abc%' 或 column LIKE '%abc' 的表达式,解析器会把模式字符串作为一个常量字面量处理,并扫描其中 % 与 _ 的位置。特别关键的是,解析器会标记“第一个通配符之前是否存在固定前缀”。如果模式以固定字符开头,例如 'abc%',解析器记录前缀为 abc,后续为可变部分;如果以 % 开头,则前缀长度为0。
这一判定结果会写入语法树节点,并传递给优化器。优化器在考虑是否使用二级索引时,本质上是看能否把Like条件转换成索引上的范围边界。B+树索引的叶子节点按键值有序排列,只有当我们知道“最小值起点”时,才能从某个位置开始顺序向后读。前缀固定的模式正好提供了这个起点,而前缀不固定则意味着任何索引项都可能是匹配项,优化器只能选择放弃索引。
可以通过 EXPLAIN 验证这一行为。在下面的示例中,我们创建一张用户表并对 name 字段建立索引,然后分别用前缀固定和前缀模糊的写法观察 type 列的变化。这能直观说明解析器给出的通配符位置信息如何决定执行路径。
CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50), KEY idx_name (name) ); -- 前缀固定,解析器判定有固定前缀,可能使用范围扫描 EXPLAIN SELECT * FROM user WHERE name LIKE '张%'; -- 前缀模糊,解析器判定前缀长度为0,通常全表扫描 EXPLAIN SELECT * FROM user WHERE name LIKE '%伟';
为什么通配符在前的模式无法利用B+树索引
要理解为何 '%abc' 不走索引,需要回到B+树的结构特性。假设 name 索引中依次存在 李四、王伟、张伟、赵刚。当我们查询 LIKE '张%' 时,优化器知道所有以“张”开头的字符串在索引里是连续的一段,可以定位到 张 的起始并向右扫描直到不以 张 开头为止,这就是典型的范围查找。
但如果是 LIKE '%伟',意味着只要结尾是“伟”就满足条件。在有序的B+树中,王伟 和 张伟 之间可能夹杂着大量不以 伟 结尾的记录,没有任何连续的物理或逻辑区间能覆盖所有候选值。优化器即便强行从索引读取,也得遍历每一个叶子节点去判断后缀,代价与全表扫描无异,因此它会直接选择扫描聚簇索引(全表)。
有些同学会问,MySQL不是有“索引下推”或“覆盖索引”吗?确实,如果查询只涉及索引列且使用了 INDEX 提示,某些版本可能在索引上做过滤,但依旧要遍历全部索引项,本质还是全索引扫描,并非通过通配符位置做的范围裁剪。解析器给出的“无固定前缀”结论,从根本上关闭了范围边界推导的可能。
-- 即使只查索引列,前缀模糊仍要扫全部索引项 EXPLAIN SELECT name FROM user WHERE name LIKE '%伟'; -- type 通常为 index(全索引扫描)而非 range
如何通过改写查询与调整结构规避索引失效
面对必须按后缀或中间内容匹配的诉求,一味依赖 LIKE '%xx' 并不可取。第一种思路是业务层拆分:如果后缀集合有限,可以冗余一个反转字段,例如 name_reverse 存储 name 的反转串,并把查询改写为 LIKE '魏%'(反转后的前缀)。这样解析器又能识别出固定前缀,正常走索引。
第二种思路是使用全文索引。MySQL从5.6起对InnoDB提供 FULLTEXT 索引,它基于倒排结构,不依赖B+树的前缀有序性,能够支持任意位置的关键字匹配。虽然全文索引的语法是 MATCH...AGAINST 而非Like,但在搜索场景中可以替代前后模糊查询,性能远优于全表扫描。
第三种是在设计阶段避免模糊后缀查询。如果系统是日志检索类,可以考虑把需要后缀检索的维度移到Elasticsearch等搜索引擎;若必须在MySQL内解决,结合生成列与索引也是可行方案。下面的例子展示用生成列存储反转串并建立索引,让解析器在改写后的查询中识别到固定前缀。
ALTER TABLE user ADD COLUMN name_reverse VARCHAR(50) GENERATED ALWAYS AS (REVERSE(name)) STORED; ALTER TABLE user ADD INDEX idx_name_reverse (name_reverse); -- 原需求:找以'伟'结尾的名字 -- 改写为反转后的前缀匹配 SELECT * FROM user WHERE name_reverse LIKE '伟%';
综上,MySQL的Like是否走索引,完全取决于解析器对通配符位置的判定结果。把通配符放在开头会抹去固定前缀,使B+树失去范围边界;通过结构冗余、全文索引或外部搜索引擎,我们能够在保留查询语义的同时,重新获得高效的检索路径。