like 'abc%'可走索引,因b+树支持前缀匹配,能定位起始点并range扫描;而'%abc'和'%abc%'无法定位起点,必须全表扫描,与配置或优化器无关。

MySQL 的 LIKE 'abc%' 能走索引,但 LIKE '%abc' 或 LIKE '%abc%' 一定不走——这不是配置问题,也不是优化器“没选对”,而是 B+ 树索引结构本身就不支持后缀或中缀查找。
为什么 LIKE 'abc%' 可以用上索引
B+ 树索引按字典序严格排序,所有键值从左到右逐字符可比。LIKE 'abc%' 提供了确定的前缀,优化器能快速二分定位到第一个 ≥ 'abc' 的索引项(比如 'abc'、'abcd'、'abce_123'),然后向右顺序遍历直到遇到 'abd' 开头的记录为止。
这个过程本质是 range 扫描,EXPLAIN 中会显示:type=range、key=xxx、rows 明显小于总行数。
- 只要前缀固定(哪怕带中文、emoji、特殊符号),都适用,例如
name LIKE '张%'、path LIKE '/api/v2%' - 组合索引中,仅最左字段满足前缀匹配才生效;
INDEX(name, age)下,WHERE name LIKE '李%'有效,WHERE age LIKE '2%'完全无效 - 若字段定义为
utf8mb4_0900_as_cs,而查询参数用了不同 collation(如utf8mb4_general_ci),可能触发隐式转换,导致索引失效
为什么 LIKE '%abc' 绝对不走索引
后缀匹配要求“结尾是 abc”,但 B+ 树里 'xabc'、'helloabc'、'abc' 这些值在物理存储上完全离散,可能分布在树的不同层级甚至不同叶子页。没有起始点,就无法利用树的有序性跳转——只能全量读取所有叶子节点做字符串截取 + 比较,这比直接全表扫描还多一层索引页 IO 开销。
EXPLAIN 必然显示:type=ALL、key=NULL、rows ≈ 表总行数。加 FORCE INDEX 也无效,不是优化器“不肯”,是引擎层压根不支持这种扫描模式。
- 常见误判:看到
type=range就以为走了索引?错。某些旧版本 MySQL 在统计信息异常时可能误标,但只要rows接近全表,实际就是伪 range,性能无改善 -
UPPER(name) LIKE '%ABC'同样失效,函数包裹会让索引键不可直接比较;可用函数索引补救:CREATE INDEX idx_name_upper ON t1 ((UPPER(name))) - 参数化拼接如
WHERE name LIKE CONCAT('%', ?),预编译阶段无法推导字面量前缀,优化器静态分析失败,索引被跳过
为什么 LIKE '%abc%' 也不走索引
中缀匹配更复杂:既无确定起点,也无确定终点。“包含 abc” 的字符串在索引中彻底无序分布,B+ 树无法构造任何有效扫描边界。即使某行数据恰好命中索引页缓存,引擎仍需逐行取出完整值,再用 CPU 做 strstr() 类判断。
它和 LIKE '%abc' 属于同一类失效场景,只是语义上更宽泛。别信“加覆盖索引就能加速”的说法——覆盖索引只省回表,不省匹配逻辑,rows 仍是全扫。
- 字段类型与参数类型不一致会雪上加霜,例如
name VARCHAR(50)被写成WHERE name LIKE 123,触发隐式转换,索引彻底作废 - 即便把 collation 强制统一(如加
COLLATE utf8mb4_bin),也改不了索引结构限制,只是避免额外转换开销 - 全文索引(
FULLTEXT)或生成列反转(REVERSE(name)+ 普通索引)是唯二可行替代路径,但各有适用边界:前者不支持短词、后者只解后缀
真正容易被忽略的是:索引是否生效,不能只看 EXPLAIN 的 type 和 key,必须核对 rows 是否显著下降。很多线上慢查表面“走了索引”,实则是 rows 几乎等于表行数,本质仍是暴力扫描——这时与其调优 SQL,不如重构查询语义或引入外部检索能力。











