like '%abc'走不了索引,因b+tree仅支持最左前缀匹配,通配符在开头导致无法定位扫描起点和范围边界,优化器只能全表扫描(type: all),key为null。

LIKE '%abc' 为什么走不了索引
因为 B+Tree 索引只支持从左到右的最左前缀匹配,LIKE '%abc' 的通配符在开头,数据库无法确定扫描起点和范围边界,优化器直接放弃索引,选择全表扫描(type: ALL)。
这不是 MySQL “没尽力”,而是索引结构本身不支持——就像字典没法按“所有以‘ang’结尾的字”快速翻页一样。哪怕 name 字段上建了 INDEX(name),EXPLAIN 里 key 字段也一定是 NULL。
全表扫描慢在哪,不只是“行数多”
真正拖慢的是 I/O 和内存压力:
- 1000 万行、每行 500 字节的表,数据量约 5GB;冷数据需从磁盘加载,SSD 顺序读 500MB/s 也要 10 秒+
- 每行都要解包、提取字段、做字符串末尾比对,CPU 开销随行数线性增长
- Buffer Pool 被大量无关数据页占满,挤出其他热点数据,引发连锁性能下降
哪些写法其实也走不了索引
别以为换了写法就能绕开限制,这些常见操作同样会让索引失效:
-
WHERE UPPER(name) LIKE '%ABC%':函数包裹字段,索引列值被改写,无法匹配 -
WHERE name LIKE '%ab%c':中间有通配符,B+Tree 无法推导连续区间 -
WHERE name LIKE 'ab%c':虽然开头固定,但%在中间,MySQL 5.7 不支持,8.0+ 优化有限且不稳定 -
WHERE name LIKE CONCAT('%', @keyword):参数拼接后仍是前导%,无实质改变
反向存储不是银弹,但能解决问题
把原始值反转后存入冗余字段,再对它做前缀匹配,是目前最可控、兼容性最好的方案:
- 写入时自动计算:
INSERT INTO users (email, email_reversed) VALUES ('user@example.com', REVERSE('user@example.com')); - 查询改写为:
WHERE email_reversed LIKE 'moc.elpmaxe@%' - 对该字段建普通索引:
CREATE INDEX idx_email_rev ON users(email_reversed)
注意:REVERSE() 是确定性函数,MySQL 5.7+ 支持函数索引,但如果你用的是旧版本或想避免冗余字段,必须手动维护 email_reversed 列——漏更新就会查不到数据,这点最容易被忽略。










