LIKE查询是否走索引取决于通配符位置:'zhang%'和'zhang_'可走索引,'%zhang'和'%zhang%'不走;全文索引不适用于结构化数据;前缀索引+LIKE'xxx%'是常用方案;复杂模糊需求可考虑生成列或函数索引。

LIKE 查询走不走索引,只看通配符位置
MySQL 的 LIKE 能否用上索引,核心就一条:左边不能有通配符。也就是模式不能以 % 或 _ 开头。一旦开头是 %user%,哪怕字段上有完整索引,优化器也基本放弃走 B+ 树索引扫描,退化为全表扫描。
常见错误现象:EXPLAIN 显示 type=ALL、key=NULL,哪怕 name 字段建了索引,WHERE name LIKE '%zhang%' 依然不走。
-
LIKE 'zhang%'→ 可走索引(范围查询,利用最左前缀) -
LIKE '%zhang'→ 不走索引(无法定位起始点) -
LIKE '%zhang%'→ 不走索引(双向模糊,完全无法剪枝) -
LIKE 'zhang_'→ 可走索引(固定前缀 + 单字符通配,仍能定位范围)
全文索引不是万能解,别乱替换成 FULLTEXT
有人一看到 LIKE '%...%' 就想换 FULLTEXT,但这是典型误用。FULLTEXT 是为自然语言文本搜索设计的,对短字段、结构化数据(如手机号、订单号、用户名)效果差,且只支持 MATCH ... AGAINST(),不兼容原有 LIKE 逻辑。
使用场景错配会导致:查不到结果、返回无关项、排序不符合预期、无法结合其他条件高效过滤。
- FULLTEXT 要求字段类型为
CHAR/VARCHAR/TEXT,且必须在MyISAM或InnoDB(5.6+)上显式创建FULLTEXT索引 - 默认最小词长为 4(
ft_min_word_len),搜'abc'直接被忽略 - 停用词(如
'the','is')不会被索引,也无法关掉(除非改配置并重建索引) -
MATCH ... AGAINST('abc*')支持后缀通配,但仅限于单词边界,对'abc123'这类混合值无效
前缀索引 + LIKE 'xxx%' 是最常用且靠谱的组合
如果业务允许“前缀匹配”,比如搜索“用户名以 zhang 开头”,那直接建普通 B+ 树索引就行,甚至可以考虑前缀索引减少体积。
性能影响明显:前缀索引比完整字段索引更小,写入更快,Buffer Pool 压力更低;但前提是前缀能区分大部分值,否则重复太多会拖慢查询。
- 建索引:
ALTER TABLE users ADD INDEX idx_name_prefix (name(10));—— 对前 10 字节建索引 - 查的时候必须严格用
LIKE 'zhang%',不能写成LIKE 'zhang_%'(下划线会被当通配符,破坏前缀语义) - 验证前缀长度是否够用:
SELECT COUNT(DISTINCT LEFT(name, 10)) / COUNT(*) FROM users;,接近 1 才安全 - 注意:前缀索引无法用于
ORDER BY name或SELECT name的覆盖索引场景(因为只存了前 10 字节)
真正需要中间匹配时,考虑生成列 + 函数索引(MySQL 5.7+)
如果非得支持 '%abc%' 且数据量大、QPS 高,又不想上 ES,可以绕道:把可能被模糊匹配的子串提前切出来,存在生成列里,再给它加索引。
例如用户昵称要支持“包含某字符串”的快速查找,可提取所有长度为 3 的连续子串(n-gram),但更实际的做法是提取高频关键词或固定偏移片段。
- 简单示例(按固定位置截取):
ALTER TABLE users ADD COLUMN name_part VARCHAR(5) STORED AS (SUBSTRING(name, 3, 5)) INDEX; - 这样
WHERE name_part = 'abcde'就能走索引,但需业务层配合把搜索词映射到对应位置 - 函数索引(8.0.13+)更灵活:
CREATE INDEX idx_name_reverse ON users ((REVERSE(name)));,可加速LIKE '%abc'(查REVERSE(name) LIKE 'cba%') - 代价是额外存储、写入变慢、维护成本上升——不是所有模糊需求都值得这么干
真正难的是权衡:要不要为 5% 的模糊查询,让 95% 的等值和前缀查询承担索引膨胀和更新开销。很多团队卡在这一步,不是不会写 SQL,而是没想清数据访问模式本身是否合理。










