like '%xxx'或'%xxx%'必然全表扫描,因b+树无法定位起点;仅'abc%'类前缀匹配可走索引,但需满足字段类型、排序规则匹配及前缀区分度足够等约束。

MySQL 中 LIKE 模糊查询慢,不是因为写法“不够高级”,而是绝大多数情况根本没走索引——尤其是 LIKE '%xxx' 或 LIKE '%xxx%' 这类写法,EXPLAIN 里 type 一定是 ALL,别信“加个索引就能快”的说法。
LIKE 'abc%' 怎么才能真走索引?
只有前缀固定、后缀通配的模式能利用 B+ 树索引的有序性。但能否真正命中,取决于三个实际约束:
- 字段类型必须是
VARCHAR或TEXT(CHAR会补空格,导致LIKE 'abc '意外匹配) - 排序规则(collation)不能破坏索引语义:比如用
utf8mb4_0900_as_cs时大小写敏感,WHERE name LIKE 'Abc%'就不会命中name上的普通索引,得写成'abc%'或显式加COLLATE - 前缀区分度太低时优化器会主动放弃索引:比如
name LIKE 'A%'匹配了 40% 的行,MySQL 认为全表扫描更快,key列仍为NULL。可用SELECT COUNT(DISTINCT LEFT(name, 1)) / COUNT(*)验证
LIKE '%abc' 或 '%abc%' 没法改写怎么办?
别硬扛,换技术方案。全文索引和反向索引是唯二靠谱的原生解法,但适用场景完全不同:
-
FULLTEXT索引只对CHAR/VARCHAR/TEXT生效,且必须用MATCH() AGAINST()——写成WHERE name LIKE '%abc%'即使有全文索引也完全不生效 - 中文必须显式指定
WITH PARSER ngram,否则默认最小词长 4,搜“李”或“AI”直接被过滤;停用词(如“的”“了”)也会被忽略,查不到先查INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD - 反向索引适合解决
LIKE '%abc':建生成列name_rev VARCHAR(100) AS (REVERSE(name)) STORED,再建普通索引idx_name_rev,查询改写为WHERE name_rev LIKE 'cba%'。但注意:LIKE '%abc%'无法用此法转化,因为反转后仍是前后都带通配符
为什么 INSTR()、LOCATE()、POSITION() 不提速?
它们只是 LIKE '%xxx%' 的语法糖,执行计划里照样是 type: ALL。MySQL 不会对函数结果自动建索引,除非你手动创建函数索引(MySQL 8.0+):
-
CREATE INDEX idx_name_instr ON t ((INSTR(name, 'abc')))没用——优化器不会把INSTR(name, 'abc') > 0映射到这个索引 - 真正有效的函数索引必须和查询完全一致:比如建了
CREATE INDEX idx_name_upper ON users ((UPPER(name))),那查询必须写成WHERE UPPER(name) = 'ABC',而不能是UPPER(name) LIKE 'AB%' - 更稳妥的做法是写入时冗余存储规范化字段(如
name_upper),查它比查函数索引更可控
多表 JOIN 带 LIKE 怎么办?
别在 SQL 里硬 JOIN 模糊条件,性能必然崩。典型错误是:
SELECT u.* FROM users u JOIN orders o ON u.id = o.user_id WHERE o.desc LIKE '%发货%'
这种写法会让 MySQL 先 JOIN 再过滤,数据量一大就卡死。正确做法是:
- 先用
SELECT id FROM orders WHERE desc LIKE '%发货%'查出匹配的order_id列表(哪怕要走全表扫描,也只扫一张表) - 再用这些 ID 去
IN或临时表关联users,避免模糊逻辑污染 JOIN 路径 - 如果模糊字段高频查询,优先考虑把
desc单独拎出来建全文索引,或接入 Elasticsearch 做异步检索
最常被忽略的一点:LIKE 查询是否走索引,跟字符集、排序规则、字段长度、匹配比例都强相关,光看有没有索引没用——EXPLAIN 里的 key 和 type 才是唯一真相。别依赖直觉,每次改写都得跑一遍 EXPLAIN 验证。











