Redis N+1问题
懂 MyBatis 的插件机制(Interceptor),可以自定义分页插件、数据脱敏插件,而不是用第三方插件踩坑;
懂 ResultMap 的映射原理,能避免 “N+1 查询”(比如合理用association/collection的懒加载、嵌套查询),而不是让查询性能指数级下降。
上面“N+1 查询”是 ORM 框架(如 MyBatis、Hibernate)中常见的性能问题,尤其是在处理一对多或多对多关联查询时容易发生。
一、什么是“N+1 查询”?
简单定义: 先执行 1 次查询获取主对象列表(N 条记录),然后对每条记录再执行一次查询来获取关联的子对象。总共执行 1 + N 次数据库查询。
二、例子说明(以 MyBatis 为例)
假设有两个表:
user用户表(id, name)order订单表(id, user_id, amount)
场景:查询所有用户及其所有订单。 错误写法(N+1 问题):
<!-- 1. 先查所有用户(1次查询,返回N条用户记录) --> <select id="selectUsers" resultMap="userMap"> SELECT id, name FROM user </select> <resultMap id="userMap" type="User"> <id property="id" column="id"/> <result property="name" column="name"/> <!-- 2. 对每个用户,执行下面的查询获取订单(N次查询!) --> <collection property="orders" column="id" select="selectOrdersByUserId"/> </resultMap> <select id="selectOrdersByUserId" resultType="Order"> SELECT id, amount FROM order WHERE user_id = #{userId} </select>
执行过程:
- 执行
SELECT id, name FROM user→ 返回 10 个用户 - 对第一个用户:
SELECT * FROM order WHERE user_id = 1 - 对第二个用户:
SELECT * FROM order WHERE user_id = 2 - ...
- 对第十个用户:
SELECT * FROM order WHERE user_id = 10
总计:1(主查询) + 10(关联查询)= 11 次数据库查询
三、如何避免 N+1 问题?
方案1:使用 JOIN 查询(推荐)
<select id="selectUsersWithOrders" resultMap="userMap"> SELECT u.id, u.name, o.id as order_id, o.amount FROM user u LEFT JOIN order o ON u.id = o.user_id </select> <resultMap id="userMap" type="User"> <id property="id" column="id"/> <result property="name" column="name"/> <collection property="orders" ofType="Order"> <id property="id" column="order_id"/> <result property="amount" column="amount"/> </collection> </resultMap>
✅ 只需 1 次查询,通过 JOIN 一次性获取所有数据。
方案2:使用 MyBatis 的 @FetchType.LAZY+ 批量加载
<collection property="orders" column="id" select="selectOrdersByUserId" fetchType="lazy"/> <!-- 延迟加载 -->
配合批量加载配置(如 MyBatis 的 lazyLoadTriggerMethods或 Hibernate 的 @BatchSize),可以在访问关联对象时批量查询而不是逐条查询。
四、性能对比
假设有 1000 个用户:
- N+1 查询:1 + 1000 = 1001 次数据库往返
- JOIN 查询:1 次数据库往返
网络延迟放大效应:如果每次查询耗时 10ms,N+1 需要 10 秒,JOIN 只需 10ms(假设 JOIN 本身不慢)。
五、要点
当问“如何避免 N+1 查询”时,可以这样回答:
- 识别场景:一对多/多对多关联查询时容易发生
- 解决方案:
- 使用 JOIN 查询替代多次单表查询
- 合理使用 懒加载 + 批量加载(
fetch="lazy"+@BatchSize) - 在 MyBatis 中,用
<collection>的resultMap属性而不是select属性
- 权衡考虑:
- JOIN 可能导致结果集冗余(用户信息重复),需在应用层去重
- 数据量极大时,JOIN 可能慢,可考虑分两次查询但用
IN语句批量查
- 监控方法:通过 MyBatis 日志查看实际执行的 SQL 条数
*
备注:公众号清汤袭人能找到我,那是随笔的地方
备注:公众号清汤袭人能找到我,那是随笔的地方

浙公网安备 33010602011771号