PHP项目LIKE查询首尾%为何不走索引

wen PHP项目 27

本文目录导读:

PHP项目LIKE查询首尾%为何不走索引

  1. 核心原因:B+树索引的“最左前缀”原则
  2. 特殊情况(MySQL为例)
  3. 解决方案
  4. 最佳实践建议

在PHP项目(或任何数据库项目)中使用 LIKE 查询时,如果模式以 开头(%keyword%keyword%),数据库索引通常会失效,导致全表扫描,这是由 B+树索引的物理存储结构决定的。

核心原因:B+树索引的“最左前缀”原则

大多数关系型数据库(MySQL、PostgreSQL等)的索引默认使用 B+树 结构。

  • B+树索引的工作方式:索引是按照从左到右的顺序对数据进行排序和存储的,当执行查询时,数据库会从索引树的根节点开始,根据值的前缀逐步向下查找。
  • LIKE 'keyword%'(尾匹配)为何能走索引LIKE 'abc%' 相当于要求“所有以 abc 开头的值”,数据库引擎可以拿着 abc 这个前缀,直接在B+树中进行范围扫描(从 abc 开始,到 abd 结束),这本质上是等值查询 + 范围查询,完全符合最左前缀原则。
  • LIKE '%keyword'(首匹配)为何不走索引LIKE '%abc' 相当于“所有以 abc 结尾的值”,数据库无法确定前缀是什么,索引中可能存储了 xabcyabczabc,要找到所有以 abc 结尾的记录,数据库必须遍历整个索引树的叶子节点,逐一检查每个值是否以 abc 遍历整个索引通常比全表扫描更慢(因为索引回表查询的额外开销),所以优化器会选择直接全表扫描。
  • LIKE '%keyword%'(模糊匹配)为何不走索引:同理,数据库不知道开头是什么,也无法利用B+树的有序性进行快速定位,它只能全索引扫描或全表扫描

特殊情况(MySQL为例)

在特定条件下,LIKE '%keyword' 可能利用 覆盖索引索引条件下推

  1. 覆盖索引(Covering Index)

    • 如果查询的所有字段都包含在同一个索引中,且查询只返回这些字段,数据库可能仍然会扫描整个索引,但不会回表,这比全表扫描快一些,但本质仍是全索引扫描(慢)。
    • SELECT id FROM users WHERE name LIKE '%abc' (假设 idname 都在联合索引中)。
  2. 索引条件下推(ICP,Index Condition Pushdown,MySQL 5.6+)

    • 对于 InnoDB 存储引擎,LIKE '%keyword' 虽然无法利用索引的排序特性,但 MySQL 可以将 WHERE 条件直接推送到存储引擎层,存储引擎在扫描索引的过程中,会提前过滤掉不符合 LIKE '%abc' 条件的行,减少回表次数,但索引本身仍然需要全扫描。

总结一句话: 在开头时,数据库无法从B+树根节点确定搜索的起始位置,只能暴力扫描,所以不走索引。

解决方案

如果必须使用首尾模糊查询且需要高性能,可以考虑以下方案:

方案 原理 适用场景 缺点
全文索引(Full-Text Index) 使用倒排索引(Inverted Index),专门为模糊搜索/分词搜索设计,语法:MATCH (col) AGAINST ('keyword') 大量文本搜索、搜索引擎类型功能 只支持 MyISAM/InnoDB;对中文需要分词器;不支持精确前缀匹配
反向索引(Reverse Index) 存储字段的反转字符串,查询 LIKE '%abc' 时,改为 LIKE 'cba%' 查反向字段。 常用后缀查询(如邮箱域名搜索 @gmail.com、路径搜索) 需要维护反转后的字段;不支持中间匹配
ES / Sphinx 外部搜索引擎(Elasticsearch、Sphinx),专门做模糊查询和分词。 大数据量、高并发、复杂模糊搜索 增加系统复杂度,需要额外运维
变更业务逻辑 %keyword% 改为 keyword%(前缀匹配)。 很多场景下,用户习惯从左到右输入(如商品名、用户名) 限制用户输入习惯,可能无法满足需求
使用 Like 加覆盖索引 确保查询字段(WHERE name LIKE '%abc')和 SELECT 字段全被覆盖 非常小的数据量或表中只有少数几行 不通用,数据量大时性能仍差

最佳实践建议

  1. 优先使用前缀匹配LIKE 'keyword%' 而非 %keyword%
  2. 考虑全文索引:对文本搜索需求,使用 FULLTEXT INDEX
  3. 数据量较小时(万级别):即使 %keyword% 全表扫描,在内存足够的情况下,MySQL 也能很快响应,不要过早优化。
  4. 数据量大时:务必使用外部搜索引擎或变更为可索引的查询方式。

LIKE '%keyword%' 不走索引是由B+树的内存结构决定的,不是bug,解决方案取决于你的具体业务场景和数据量。

抱歉,评论功能暂时关闭!