LIKE '%abc'一定不走B+树索引,因B+树需确定前缀才能定位扫描起点,而左模糊无法提供该前缀,故必全表扫描,EXPLAIN显示type: ALL、key: NULL。

LIKE '%abc' 一定不走 B+ 树索引,不是写法问题,是 MySQL 索引结构决定的硬限制——别试 FORCE INDEX,也别调统计信息,它就是没法定位扫描起点。
为什么 LIKE '%abc' 在 EXPLAIN 中显示 type: ALL、key: NULL
B+ 树索引按字典序存储值,查找必须从一个确定前缀开始向右遍历。LIKE 'abc%' 能转成 name >= 'abc' AND name ,优化器可直接跳到起始页;而 LIKE '%abc' 要匹配“结尾为 abc”的字符串,索引里没存反向顺序,没有锚定点。哪怕字段上有 <code>INDEX(name),也完全不会被使用。
常见错误现象包括:
- 加了
FORCE INDEX(idx_name)后执行计划仍为type: ALL - 复合索引
(status, name)对name LIKE '%abc'完全无效——name不在最左位置,且模式不满足前缀匹配 -
REGEXP 'abc$'行为一致,同样不走索引
哪些情况看似走了索引,其实只是“假走”
执行计划出现 type: index、key: idx_name,不代表真高效——那只是**索引覆盖扫描(index scan)**,即只读索引树,不回表,但仍是全量遍历该索引所有叶子节点。
能触发这种“假走”的前提是:
- 查询字段全部包含在索引定义中,例如索引是
INDEX(name, id, email) - SQL 写成
SELECT name, id FROM t WHERE name LIKE '%abc%'—— 满足覆盖,type变成index - 一旦多查一个没进索引的字段,比如
SELECT name, id, created_at,而created_at不在索引里,就立刻退化为type: ALL
注意:type: index 比 type: ALL 略快(因避免回表 I/O),但仍是 O(N) 复杂度,数据量一过十万,延迟就明显上升。
真正有效的绕过方案:不硬扛,换思路
要让 LIKE '%abc' 类查询变快,核心是把“后缀匹配”或“中间匹配”转成“前缀匹配”,或者干脆换索引类型。
- 对短字段(如邮箱、域名):建冗余列
email_reversed,写入时自动填REVERSE(email),再建普通索引INDEX(email_reversed),查时写成email_reversed LIKE 'moc.elpmaxe@%' - 对中长文本(标题、简介):弃用 LIKE,改用
FULLTEXT索引 +MATCH(name) AGAINST('abc' IN NATURAL LANGUAGE MODE);注意中文需 MySQL 8.0.30+ 或配置ngram插件,且默认ft_min_word_len = 4,搜“go”会查不到 - MySQL 8.0+ / PostgreSQL:直接建函数索引,如
CREATE INDEX idx_domain ON users ((SUBSTRING_INDEX(email, '@', -1))),查时用SUBSTRING_INDEX(email, '@', -1) = 'qq.com' - 高频、复杂模糊需求(支持高亮、分词、相关性排序):把查询交给 Elasticsearch 或 Meilisearch,MySQL 只作数据源
最容易被忽略的隐性失效点
即使你写了 LIKE 'abc%',也可能因以下原因悄悄失效:
- 字段是
utf8mb4_0900_as_cs,但查询字面量没声明 collation,触发隐式转换,key在 EXPLAIN 中变成NULL - 用了函数包裹:
UPPER(name) LIKE 'ABC%'或TRIM(name) LIKE 'abc%'→ 索引必然失效 - 联合索引为
(a, b, c),但 WHERE 条件是a = 1 AND c LIKE 'x%'→ 只用到a,c被跳过(违反最左前缀) - 字段定义为
VARCHAR(255),但平均长度仅 8,建索引应写成INDEX(name(10)),否则索引体积大、缓存效率低
真正的瓶颈从来不在 LIKE 本身,而在你没意识到「B+ 树只认左前缀」这个边界——一旦业务要求任意位置匹配,就得接受:MySQL 不是搜索引擎,该交出去的查询,就别硬留在 SQL 层里扛。

















