一、为什么今天还要谈 MyBatisPlus 性能
在 CRUD 代码已被极大简化的当下,“性能”仍然是悬挂在开发者头上的达摩克利斯之剑:一条慢 SQL、一次缓存雪崩、一个配置缺漏,都可能让千万级请求瞬间溃堤。MyBatisPlus 将手写 SQL 的灵活与 ORM 的便捷融为一体,却也让“黑盒”变大——一旦变慢,排查链路更长。本文结合最新生产案例与官方实践,拆解 MyBatisPlus 从 SQL 生成到数据库返回结果的全生命周期瓶颈,并给出可落地的优化清单。
二、性能画像:一条查询的五个阶段
1. 解析阶段
MyBatisPlus 将 Lambda 条件、Wrapper 组装为 SQL 片段;若条件复杂,动态 SQL 节点过多,解析耗时增加。
2. 传输阶段
Java 到 MySQL 的网络 RTT、连接池排队、SSL 握手都会放大延迟。
3. 执行阶段
数据库收到 SQL 后,解析、优化、执行计划生成、索引选择、回表、排序、锁等待。
4. 结果回传阶段
大字段、深分页、N+1 次查询导致大量数据跨网络拷贝。
5. 对象映射阶段
ResultSet → 实体类反射填充、TypeHandler 转换、自动填充字段触发。
只有逐段拆解,才能对症下药。
三、批量插入:从 20 分钟到 40 秒的跃迁
场景:在线考试系统一次导入 50 万条题目与选项。
默认 saveBatch 逐条发送,网络往返 + 语法解析导致 20 min+。
优化组合拳:
1. 驱动层
开启 rewriteBatchedStatements=true,把 1000 条单 insert 改写成一条 multi-values SQL,网络往返减少 1000 倍。
2. ID 策略
预生成雪花 ID,避免 insert 后再回写主键,减少一次往返。
3. 分批大小
每批 3000 条为 Sweet Spot(过高会触发 MySQL 包大小限制)。
4. 执行器
ExecutorType.BATCH 替代默认 SIMPLE,JDBC 批处理真正生效。
5. 多线程
8 核 CPU 开 4-8 个线程并行导入,注意事务拆分,避免锁冲突。
最终实测 50 万条插入耗时 40 秒,性能提升 2000%。
四、深分页:OFFSET 是性能杀手
传统 PageHelper startPage 第 1 万页时,MySQL 需扫描并丢弃前 9990 行。
替代方案:
• 游标分页(Keyset Pagination)
SELECT * FROM exam WHERE id > #{lastId} ORDER BY id ASC LIMIT 20;
借助索引定位,不回溯。
• 子查询定位边界
SELECT * FROM exam WHERE id > (SELECT id FROM exam ORDER BY id LIMIT 9990, 1) LIMIT 20;
适用于无法改接口的分页组件。
• 延迟关联
先查询主键列表,再按主键 IN 查询详情,避免回表大字段。
五、查询性能:索引与 SQL 结构
1. 索引覆盖
SELECT id, name FROM user WHERE status = 1;
若 status 与 name 联合索引可覆盖,无需回表。
2. 避免函数索引失效
WHERE DATE(create_time) = '2024-05-01' 会放弃索引;
改写为 create_time BETWEEN '2024-05-01 00:00:00' AND '2024-05-01 23:59:59'。
3. 排序优化
ORDER BY create_time DESC, id ASC 利用联合索引 (create_time DESC, id ASC) 可避免文件排序。
4. 查询字段精简
杜绝 SELECT *,仅查询所需列,减少网络与内存拷贝。
六、缓存体系:三级加速
1. 一级缓存
SqlSession 级 HashMap,单次会话内重复查询免数据库。
2. 二级缓存
Mapper 级 LRU,跨会话共享;注意缓存穿透与失效策略。
3. 分布式缓存
Redis + 本地 Caffeine 双层缓存,解决集群命中率。
热点 Key 采用布隆过滤器防击穿,更新采用 Canal 异步失效。
七、对象映射:反射与填充
• 元数据缓存
MyBatisPlus 在启动时把表字段→属性的映射缓存,避免每次反射解析。
• 自动填充关闭
批量插入时若无需 update_time,可在 MetaObjectHandler 中判断场景,减少一次 UPDATE。
• TypeHandler 优化
JSON 字段使用 FastjsonTypeHandler 比 Jackson 快 15%,且内存占用更低。
八、连接池调优
1. 连接数公式
并发请求 × (平均 SQL 执行时间 + 网络 RTT) / 1s = 理论连接数。
2. 超时参数
connectionTimeout=2s、validationQuery=SELECT 1、idleTimeout=30min,防止慢连接堆积。
3. 探活策略
HikariCP 的 keepaliveTime + MySQL 8.0 的 tcp_keepalive_time 配合,避免防火墙空闲断开。
九、监控与诊断
• 指标
慢 SQL 阈值 100 ms、全表扫描告警、锁等待 > 200 ms。
• 链路追踪
SkyWalking 自动把 Mapper 方法 → SQL → 数据库耗时串联。
• 日志脱敏
通过 MyBatisPlus 的 SQL 解析器拦截器,敏感字段自动替换为 “***”。
• 动态 SQL 审计
Git 提交触发 SQL Reviewer,自动检测 SELECT *、无索引 WHERE。
十、综合调优路线图
1. 基线:跑一次全量压测,记录 QPS、P99、CPU、内存、网络。
2. 分段:
• 应用层:SQL 改写、索引设计、缓存策略;
• 驱动层:批处理、连接池、超时参数;
• 数据库层:参数调优、硬件升级、读写分离。
3. 回归:每次改动后跑同场景压测,确保收益 ≥ 10 % 才合并主干。
4. 固化:将最佳参数沉淀为模板,纳入自动化测试,防止后续回归。
十一、总结
MyBatisPlus 让 CRUD 变得优雅,却也把性能优化拆成更细的拼图:一条正确的 JDBC 参数、一个恰到好处的索引、一次命中缓存的查询,都会把用户体验向前推一大步。把本文的“五阶段画像”与“路线图”带回项目,你将拥有一张可随时展开的性能作战地图,而非临时救火。