三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

MybatisPlus模糊查询性能优化:当`like`遇上多值匹配,如何避免全表扫描?

MybatisPlus模糊查询性能优化:当`like`遇上多值匹配,如何避免全表扫描?

MybatisPlus模糊查询性能优化:多字段匹配的深度实践

在数据驱动的现代应用中,模糊查询几乎是每个系统都无法绕开的功能需求。当数据量达到百万级甚至更高时,一个简单的LIKE '%keyword%'操作就可能成为系统性能的"阿喀琉斯之踵"。特别是当业务需求要求在多个字段上进行模糊匹配时,问题会变得更加复杂——索引失效、全表扫描、执行计划劣化等问题接踵而至。

1. 多字段模糊查询的性能陷阱

1.1 OR条件与索引失效

MybatisPlus中常见的多字段模糊查询写法是这样的:

QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.like("username", "张") .or() .like("email", "example") .or() .like("phone", "138");

这种链式OR操作生成的SQL类似于:

SELECT * FROM user WHERE username LIKE '%张%' OR email LIKE '%example%' OR phone LIKE '%138%'

问题核心在于:

  • 前导通配符%导致索引失效
  • OR条件使得优化器难以选择最优执行计划
  • 多字段组合查询时,数据库可能选择全表扫描

1.2 执行计划分析对比

我们通过EXPLAIN分析不同写法的执行计划差异:

查询类型索引使用情况扫描行数执行时间
单字段右模糊使用索引1,20015ms
多字段OR模糊全表扫描1,000,0001,200ms
多字段UNION ALL部分使用索引3,60045ms

提示:在MySQL中,LIKE 'value%'可以使用索引,但LIKE '%value%'会导致索引失效

2. 优化策略与实践方案

2.1 查询结构调整

策略一:UNION ALL替代OR

// 方案1:分别查询后合并结果 List<User> result = new ArrayList<>(); result.addAll(userMapper.selectList(new QueryWrapper<User>().like("username", "张"))); result.addAll(userMapper.selectList(new QueryWrapper<User>().like("email", "example"))); result.addAll(userMapper.selectList(new QueryWrapper<User>().like("phone", "138"))); // 方案2:使用UNION ALL SQL @Select("SELECT * FROM user WHERE username LIKE '%${keyword}%' " + "UNION ALL SELECT * FROM user WHERE email LIKE '%${keyword}%' " + "UNION ALL SELECT * FROM user WHERE phone LIKE '%${keyword}%'") List<User> searchByMultipleFields(@Param("keyword") String keyword);

策略二:条件预筛选

// 先通过精确匹配缩小范围 List<Integer> userIds = userMapper.selectObjs( new QueryWrapper<User>() .select("id") .eq("status", 1) .between("create_time", startDate, endDate) ).stream().map(o -> (Integer)o).collect(Collectors.toList()); // 再在缩小后的范围内做模糊查询 if(!userIds.isEmpty()) { queryWrapper.in("id", userIds) .and(wrapper -> wrapper .like("username", "张") .or() .like("email", "example")); }

2.2 MybatisPlus 3.x特性应用

Lambda表达式条件构造

// 更安全的类型安全写法 List<User> users = userMapper.selectList(Wrappers.<User>lambdaQuery() .and(qw -> { for(String keyword : keywords) { qw.or().like(User::getUsername, keyword) .or().like(User::getEmail, keyword); } }) .eq(User::getIsDeleted, 0));

条件构造器优化技巧

// 动态条件构建 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); boolean hasCondition = false; if(StringUtils.isNotBlank(username)) { queryWrapper.like(rightLike(username), "username"); hasCondition = true; } if(StringUtils.isNotBlank(email)) { if(hasCondition) queryWrapper.or(); queryWrapper.like(rightLike(email), "email"); hasCondition = true; } // 辅助方法:避免前导通配符 private String rightLike(String value) { return value.endsWith("%") ? value : value + "%"; }

3. 高级优化方案

3.1 引入全文索引

对于频繁的模糊查询场景,考虑使用专业搜索技术:

-- MySQL全文索引 ALTER TABLE user ADD FULLTEXT INDEX ft_search (username, email, phone); -- 使用MATCH AGAINST语法 SELECT * FROM user WHERE MATCH(username, email, phone) AGAINST('+张 +example' IN BOOLEAN MODE);

MybatisPlus中集成:

@Select("SELECT * FROM user WHERE MATCH(username, email, phone) " + "AGAINST(#{query} IN BOOLEAN MODE)") List<User> fullTextSearch(@Param("query") String query);

3.2 异步查询与缓存策略

实现方案:

  1. 用户发起搜索请求
  2. 系统立即返回空结果+查询ID
  3. 后台异步执行复杂查询
  4. 结果缓存到Redis并通知前端
  5. 前端通过WebSocket或轮询获取结果
// 伪代码示例 public SearchResult searchAsync(SearchParams params) { String searchId = UUID.randomUUID().toString(); // 放入消息队列异步处理 mqTemplate.send("search.queue", new SearchTask(searchId, params)); // 立即返回 return new SearchResult(searchId, Collections.emptyList()); } // 消费者处理 public void handleSearchTask(SearchTask task) { List<User> results = complexSearch(task.getParams()); redisTemplate.opsForValue().set("search:"+task.getSearchId(), results); notifyClient(task.getSearchId()); }

4. 实战案例:电商用户搜索优化

某电商平台用户表(2000万数据)的多字段搜索优化实践:

原始方案:

queryWrapper.like("nickname", keyword) .or().like("mobile", keyword) .or().like("email", keyword);
  • 平均响应时间:3.2秒
  • CPU使用率峰值:90%

优化方案:

  1. 建立(nickname, mobile, email)的组合索引
  2. 使用UNION ALL拆分查询
  3. 增加结果缓存
if(isComplexSearch(keyword)) { // 复杂查询走异步流程 return searchAsync(keyword); } else { // 简单查询使用优化后的UNION方案 return fastSearch(keyword); }

优化后指标:

  • 平均响应时间:480ms
  • CPU使用率峰值:35%
  • 99线延迟:<1秒

配置参考:

# MybatisPlus配置 mybatis-plus.global-config.db-config.logic-not-delete-value=0 mybatis-plus.global-config.db-config.logic-delete-value=1 # 连接池配置 spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.connection-timeout=30000

在具体实现时,我们发现当查询条件超过3个OR组合时,使用UNION ALL方案性能优势更加明显。但对于小数据量表(10万行以下),简单的OR查询可能更合适,需要根据实际业务场景进行权衡。

← 返回列表