带有LIKE模糊匹配的分组查询在业务开发中十分常见,比如统计不同前缀的商品名称数量、筛选特定格式的日志信息并分组统计等场景,这类查询如果不做优化,很容易出现执行耗时过长的问题。传统方案下,数据库需要对全表数据进行扫描,逐行匹配LIKE条件后再做分组计算,当表数据量达到百万级以上时,查询耗时可能超过数秒,严重影响业务响应速度。

常规LIKE分组查询的性能问题
我们先来看一个典型的未优化的查询场景,假设有一张product表,存储了商品的基础信息,其中product_name是商品名称字段,现在需要统计所有以"手机"开头的商品名称对应的商品数量,常规SQL写法如下:
-- 未优化的LIKE前缀匹配分组查询 SELECT product_name, COUNT(*) AS product_count FROM product WHERE product_name LIKE '手机%' GROUP BY product_name;
如果product_name字段没有合适的索引,这条语句会触发全表扫描,对每一行的product_name做字符串匹配,匹配成功后再参与分组计算。即使product_name有普通索引,LIKE以通配符开头的查询(比如'%手机%')也无法使用索引,只有前缀匹配的场景('手机%')才有可能使用到普通索引,但分组操作依然可能带来额外的排序开销。
利用前缀索引优化前缀匹配分组查询
如果LIKE查询都是前缀匹配的场景,也就是通配符只在末尾,比如'手机%'、'电脑%'这类,那么可以创建前缀索引来优化查询性能。前缀索引只对字段的前N个字符建立索引,相比全字段索引占用更小的存储空间,同时能提升匹配效率。
前缀索引的创建方式
创建前缀索引时需要选择合适的前缀长度,既要保证索引的选择性足够高,又要控制索引的大小。可以通过下面的SQL计算不同前缀长度的选择性:
-- 计算product_name不同前缀长度的选择性 SELECT COUNT(DISTINCT LEFT(product_name, 3)) / COUNT(*) AS sel3, COUNT(DISTINCT LEFT(product_name, 5)) / COUNT(*) AS sel5, COUNT(DISTINCT LEFT(product_name, 7)) / COUNT(*) AS sel7, COUNT(DISTINCT LEFT(product_name, 10)) / COUNT(*) AS sel10 FROM product;
当某个前缀长度的选择性接近全字段的选择性时,就可以选择这个长度作为前缀索引的长度。假设计算后发现前缀长度为7时选择性已经足够高,那么可以创建前缀索引:
-- 创建product_name的前缀索引,前缀长度为7 CREATE INDEX idx_product_name_prefix ON product(product_name(7));
优化后的查询效果
创建前缀索引后,再执行之前的LIKE前缀匹配分组查询,数据库会优先使用前缀索引快速定位符合'手机%'条件的记录,减少全表扫描的开销,分组操作也可以利用索引的有序性减少排序成本,查询耗时通常会下降一个数量级。
需要注意的是,前缀索引只适用于LIKE前缀匹配的场景,如果LIKE查询是包含匹配('%手机%')或者后缀匹配('%手机'),前缀索引是无法生效的,这时候就需要考虑全文索引方案。
利用全文索引优化包含匹配分组查询
当LIKE查询是包含匹配或者后缀匹配,无法通过前缀索引优化时,全文索引是更合适的方案。全文索引会对文本字段的内容进行分词处理,建立倒排索引,能够快速定位包含指定关键词的记录,相比LIKE的逐行字符串匹配效率要高很多。
全文索引的创建与使用
首先需要在product_name字段上创建全文索引,不同数据库的全文索引语法略有差异,以MySQL为例:
-- 创建product_name字段的全文索引 CREATE FULLTEXT INDEX idx_product_name_fulltext ON product(product_name);
创建完成后,就可以使用全文索引的匹配语法来替换原来的LIKE查询,统计包含"手机"关键词的商品名称数量:
-- 使用全文索引的包含匹配分组查询
SELECT product_name, COUNT(*) AS product_count
FROM product
WHERE MATCH(product_name) AGAINST('手机' IN BOOLEAN MODE)
GROUP BY product_name;
全文索引的注意事项
- 全文索引对短文本的分词效果可能有限,比如单个字符或者过短的关键词可能无法正确匹配,需要根据实际业务调整分词规则。
- 全文索引的更新会有一定开销,如果表是写多读少的场景,需要评估索引维护对写入性能的影响。
- 不同数据库对全文索引的支持程度不同,比如MySQL的全文索引对中文的支持需要开启ngram分词器,创建索引时需要指定分词长度,例如
CREATE FULLTEXT INDEX idx_product_name_fulltext ON product(product_name) WITH PARSER ngram;。
两种优化方案的对比与选择
我们可以通过下面的表格对比两种优化方案的适用场景和特点:
| 优化方案 | 适用LIKE场景 | 索引大小 | 维护成本 | 性能提升幅度 |
|---|---|---|---|---|
| 前缀索引 | 仅前缀匹配('关键词%') | 较小 | 低 | 高 |
| 全文索引 | 前缀、包含、后缀匹配均支持 | 较大 | 中 | 极高(尤其是包含匹配场景) |
在实际业务中选择方案时,首先判断LIKE查询的模式,如果是固定的前缀匹配场景,优先选择前缀索引,成本和收益比更优;如果包含多种匹配模式,或者主要是包含、后缀匹配,那么选择全文索引更合适。同时要注意定期分析查询的执行计划,通过EXPLAIN命令查看索引是否被正确使用,及时调整索引策略。
优化后的效果验证
可以通过EXPLAIN命令对比优化前后的查询执行计划,优化前可能出现type: ALL的全表扫描标识,优化后应该出现type: range或者type: fulltext的索引使用标识,同时rows字段的扫描行数会大幅下降。比如优化前扫描100万行,优化后可能只需要扫描几千行,查询耗时从3秒下降到100毫秒以内,性能提升非常明显。