like '%abc'无法使用b+树索引,因其仅支持从左到右的前缀匹配;解决方案是添加stored生成列name_rev as reverse(name),建前缀索引idx_name_rev(name_rev(20)),查询改写为name_rev like concat(reverse('txt'), '%')。

LIKE '%abc' 后缀匹配为什么走不了索引
B+ 树索引按字典序组织数据,只能从左到右做前缀比较。写成 LIKE '%abc' 时,MySQL 无法确定扫描起点——“abc”可能出现在任意位置,必须逐行检查字段值,EXPLAIN 里必然出现 type: ALL 和 key: NULL。
反转字符串 + 前缀索引是可行解,但得严格按步骤来
核心思路是把后缀问题转为前缀问题:原字段值 'filename.txt' → 反向存为 'txet.elifenam',查 '%txt' 就变成查 REVERSE('txt') || '%',即 'txt%' 。
- 添加生成列:
ALTER TABLE files ADD COLUMN name_rev VARCHAR(255) AS (REVERSE(name)) STORED; - 建前缀索引(注意长度):
CREATE INDEX idx_name_rev ON files(name_rev(20));—— 不要直接name_rev全长索引,否则浪费空间且区分度未必高 - 查询必须改写:
SELECT * FROM files WHERE name_rev LIKE CONCAT(REVERSE('txt'), '%');,不能漏掉CONCAT或写成REVERSE('txt') + '%'(MySQL 不支持字符串拼接用+) - 确保
name列本身有索引或主键,否则STORED生成列在 INSERT/UPDATE 时无法自动维护
覆盖索引能缓解,但不解决根本问题
有人试过对原字段建联合索引并只查索引列,比如 INDEX(name, id) 然后执行 SELECT name, id FROM files WHERE name LIKE '%txt',EXPLAIN 可能显示 Using index。但这只是避免了回表,并没跳过全表扫描——type 还是 ALL,性能瓶颈仍在。
- 覆盖索引只减少 I/O,不减少扫描行数
- 当表超过 10 万行,这种“伪优化”和全表扫差别不大
- 真正想提速,必须让
WHERE条件能定位起始位置,也就是回到前缀匹配逻辑
容易忽略的字符集与长度陷阱
UTF8MB4 下一个汉字占 4 字节,name_rev(20) 实际可能只截取 5 个汉字;如果原始 name 平均长度 100 字符,但前 20 个反向字符重复率高(比如大量文件以 .log 结尾),那 name_rev(20) 索引区分度会很低。
- 务必先验证区分度:
SELECT COUNT(DISTINCT LEFT(name_rev, 20)) / COUNT(*) FROM files;,结果低于 0.9 就别用这个长度 - 生成列必须用
STORED,不能用VIRTUAL——后者不落盘,无法建索引 -
REVERSE()对含 emoji 或混合编码的字段可能出错,上线前用真实数据抽样测试
实际落地时,最常被跳过的一步是:没确认查询是否真的命中了 idx_name_rev。加完索引立刻跑 EXPLAIN,看 key 列是不是它,rows 是否明显下降——否则所谓“优化”,只是多建了一条没用的索引。











