目标:你能把 MyBatis 的性能问题拆成"SQL 本身 + JDBC/网络 + MyBatis 使用方式"三层,并掌握可落地的优化点。
1. 性能问题先分层:不要一上来就调 MyBatis
MyBatis 慢,常见根因优先级通常是:
- SQL/索引设计(占大头)
- 连接池/网络/DB 负载
- MyBatis 使用方式(N+1、错误分页、批处理不当)
面试时你要体现"先定位再优化"的工程能力。
2. N+1 问题:最常见的 ORM 性能坑
典型场景:
- 查主表 N 条,再对每条查一次明细表
- 日志里表现为一堆重复 SQL
解决方案:
- 一次性 join 或 IN 批量查询
- 用
resultMap做聚合映射(注意去重) - 业务层做批量预取
排查手段:
- 打开 SQL 日志,观察是否出现"同模板 SQL 重复执行 N 次"
- 用链路追踪看 DB span 数量是否异常
3. 分页:你以为用了分页,其实在内存分页
3.1 正确分页:让 DB 做 limit/offset 或基于游标
limit offset, size简单但 offset 大时会慢- 大翻页建议:
- 基于主键/索引的"seek"方式(where id > lastId limit size)
3.2 常见坑:不小心触发全量查询
- 没有加 limit
- PageHelper/拦截器没生效(配置顺序、插件冲突)
排查:
- 看最终发到 DB 的 SQL 是否有 limit
4. 批处理:减少网络往返,但别把内存打爆
4.1 批处理的收益
- 单条插入 1000 次 = 1000 次网络往返
- 批处理 = 1 次或少量往返
4.2 常见误区
- 一次攒太大批次:
- MyBatis/JDBC 会缓存参数,容易占用大量内存
- 没有控制事务:
- 批处理通常应显式事务,否则每条 auto-commit 性能差
建议:
- 分批提交(例如 500/1000 一批)
- 明确事务边界
- 监控单次批处理耗时与失败重试策略
5. 缓存:一级缓存别神化,二级缓存要谨慎
5.1 一级缓存(SqlSession 级别)
- 同一个
SqlSession内重复查询可命中 - 默认开启
坑:
- 与事务/会话生命周期强相关
- 在 Web 应用中通常每次请求一个 SqlSession,收益有限
5.2 二级缓存(Mapper 级别)
- 跨 SqlSession
- 需要显式开启,且对象要可序列化
风险:
- 一致性:更新后缓存失效策略复杂
- 命中率:命中低反而多一次序列化开销
建议:
- 更推荐把缓存放到 Redis/本地缓存层,用更可控的失效策略
- 二级缓存只用于少量稳定读、低写场景
6. 慢 SQL 定位:从 MyBatis 到数据库的闭环
6.1 你需要的三类证据
- 应用侧:SQL 模板、参数、耗时、调用栈
- DB 侧:慢日志、执行计划、锁等待
- 链路侧:某接口中 DB 调用次数与分布
6.2 常见慢因
- 索引失效(隐式类型转换、函数包裹列、like 前缀
%) - 返回列过多(select *)
- 大 offset 分页
- 锁等待(更新热点行)
6.3 MyBatis 相关的定位点
- 是否存在大量小 SQL(N+1)
- 是否事务过大导致锁持有时间长
- 是否执行了不必要的 flush
7. 参数与映射:性能与正确性的隐形成本
- 大字段(TEXT/BLOB)不要随意查出来
- 合理使用延迟加载(谨慎:容易触发 N+1)
- resultMap 的嵌套映射要注意去重与集合膨胀
8. 面试背诵稿(45 秒)
MyBatis 性能优化我会先分层:优先看 SQL 和索引,再看连接池与 DB 负载,最后才是 MyBatis 使用方式。
最常见的问题是 N+1,我会用 join 或 IN 批量预取解决;分页要确保最终 SQL 带 limit,并且大翻页用基于索引的 seek 分页。
批处理能显著减少网络往返,但要控制批次大小并明确事务边界避免内存膨胀。
缓存方面一级缓存作用受 SqlSession 生命周期限制,二级缓存一致性成本高,通常更建议用独立缓存层。
定位慢 SQL 时要把应用侧 SQL+参数、DB 执行计划和链路调用次数三类证据闭环。