当前位置: 首页 > news >正文

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查询可能更合适,需要根据实际业务场景进行权衡。

http://www.jsqmd.com/news/762132/

相关文章:

  • 2026年体育看台施工服务排名,费用低的公司盘点 - mypinpai
  • PTA天梯赛L2-016题保姆级攻略:用DFS搞定‘五服禁婚’判断(附C++完整代码)
  • ViC框架:零样本视频语义检索技术解析与实践
  • 快速验证单片机tlsf内存管理,快马一键生成stm32适配原型
  • FlowiseAI:可视化低代码平台,快速构建LLM应用与AI智能体
  • 告别Monkey的随机乱点:用Android Maxim给你的App做一次深度压力测试(附雪球App实战)
  • Hotkey Detective:Windows热键冲突的终极解决方案,快速找回被占用的快捷键
  • 告别手写接口代码:用快马平台实现OpenSpec文档驱动的高效开发
  • Simapro参数化分配实战:用‘开关’一键切换LCA中的质量与经济分配
  • 比较好的特灵空调服务区域 - mypinpai
  • 保姆级教程:在GAMMA中为Sentinel-1数据做地理编码,从DEM导入到生成地理坐标影像的全流程详解
  • 嵌入式开发提效神器:一个框架整合命令行、低功耗与设备管理(基于IAR/Keil)
  • 从CT到病理切片:手把手教你用Stable Diffusion的“亲戚”搞定多模态医学图像生成
  • Arm SAM寄存器模型架构与安全事件管理机制解析
  • Emacs AI编程统一接口:ai-code-interface.el 深度解析与实战指南
  • AI对话系统安全防护:实时反馈与提示工程实践
  • SAP屏幕开发避坑指南:PBO/PAI逻辑流搞不清?这5个常见错误别再犯了
  • VStyle语音风格适配框架:原理、实现与应用
  • 新手福音:在快马平台上用OpenClaw完成你的第一个网页抓取程序
  • 实战指南:基于快马AI辅助,从零构建Vivado UART-SPI数据采集显示系统
  • 告别VSCode C++插件卡顿!ROS开发用clangd实现丝滑补全的保姆级配置
  • 从零到编译成功:手把手教你用VS2019和最新工具链配置EDK2开发环境(2023版)
  • 开发者必备设计技能:从原则到代码的完整学习路径与实践指南
  • 从图像处理到机器学习:NumPy ndarray的5个‘骚操作’,让你的代码更简洁高效
  • S32K3的BIST自测功能怎么用?手把手教你配置MCAL的Bist模块(附代码避坑点)
  • 大语言模型在医疗分诊中的应用与优化
  • OpenClaw 2.6.6 版本安装指南 小白也能学会的保密级配置
  • 从SWPUCTF 2023新生赛看Web安全考点:PHP、SQL、反序列化漏洞实战避坑指南
  • RocketMQ系列第三篇:Java原生基础使用实操,手把手写生产者消费者Demo
  • 多模态表格问答技术:原理、实现与应用场景