全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-20 8 分钟 ✍️ juanwangdev

嵌套查询与 N+1 问题

嵌套查询通过 select 属性触发子查询获取关联数据,但不当使用会导致 N+1 问题,严重影响性能。

嵌套查询原理

XML
<resultMap id="blogResultMap" type="Blog">
  <id property="id" column="id"/>
  <result property="title" column="title"/>
  <association property="author"
               column="author_id"
               javaType="Author"
               select="selectAuthor"/>
</resultMap>

<select id="selectBlog" resultMap="blogResultMap">
  SELECT * FROM blog WHERE id = #{id}
</select>

<select id="selectAuthor" resultType="Author">
  SELECT * FROM author WHERE id = #{id}
</select>

执行流程:

  1. 执行 selectBlog 查询博客信息。
  2. 读取 author_id 列值。
  3. author_id 传入 selectAuthor,查询作者信息。

N+1 问题

当查询返回 N 条主记录时,每条记录的关联属性都会触发一次子查询,导致 1 + N 次数据库访问:

Java
// 1 次查询获取 100 篇博客
List<Blog> blogs = mapper.selectBlogList();

// 每篇博客触发 1 次作者查询,共 100 次
for (Blog blog : blogs) {
  System.out.println(blog.getAuthor().getName());
}
// 总计:1 + 100 = 101 次查询

N+1 问题在使用子查询方式(select 属性)且访问关联属性时自动触发。

优化方案一:使用 Join

用一条 JOIN 替代 N+1 次查询:

XML
<resultMap id="blogAuthorResult" type="Blog">
  <id property="id" column="blog_id"/>
  <result property="title" column="blog_title"/>
  <association property="author" javaType="Author">
    <id property="id" column="author_id"/>
    <result property="name" column="author_name"/>
  </association>
</resultMap>

<select id="selectBlogsWithAuthors" resultMap="blogAuthorResult">
  SELECT b.id blog_id, b.title blog_title,
         a.id author_id, a.name author_name
  FROM blog b
  LEFT JOIN author a ON b.author_id = a.id
</select>

Join 方式将 N+1 次查询降为 1 次,是最直接的优化方案。

优化方案二:延迟加载(懒加载)

不访问关联属性时不触发子查询:

XML
<!-- mybatis-config.xml -->
<settings>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="aggressiveLazyLoading" value="false"/>
</settings>
XML
<resultMap id="blogResultMap" type="Blog">
  <id property="id" column="id"/>
  <result property="title" column="title"/>
  <association property="author"
               column="author_id"
               javaType="Author"
               select="selectAuthor"
               fetchType="lazy"/>
</resultMap>
fetchType 值行为
lazy延迟加载,访问属性时才查询
eager立即加载,查询主数据时同时查询

aggressiveLazyLoading=false 时仅访问关联属性才加载;若为 true,调用任意方法都会触发全部关联加载。

优化方案三:批量查询

MyBatis 3.5+ 支持 multi fetchType,批量收集关联 ID 后一次性 IN 查询:

XML
<association property="author"
             column="author_id"
             javaType="Author"
             select="selectAuthor"
             fetchType="lazy"/>
XML
<!-- mybatis-config.xml -->
<settings>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

三种方案对比

方案SQL 次数适用场景缺点
Join1 次数据量小,关联简单列名冲突需别名
懒加载按需关联数据使用不确定需合理配置触发策略
批量查询2 次N 较大且 IN 查询高效需 IN 查询支持

嵌套查询传递多列

关联查询需要多列时,使用 {} 语法:

XML
<collection property="comments"
            column="{postId=id, status=state}"
            ofType="Comment"
            select="selectComments"/>

<select id="selectComments" resultType="Comment">
  SELECT * FROM comments
  WHERE post_id = #{postId} AND status = #{status}
</select>

要点总结

  • 嵌套查询通过 select 属性触发子查询,存在 N+1 风险。
  • N+1 问题:1 次主查询 + N 次关联查询,性能严重下降。
  • Join 优化:一次查询完成,适合数据量小的场景。
  • 懒加载:按需加载,通过 fetchType="lazy" 配置。
  • 批量查询:收集关联 ID 后 IN 查询,减少 SQL 次数。
  • 传递多列参数使用 {key1=col1, key2=col2} 语法。

📝 发现内容有误?点击此处直接编辑

← 上一篇 多对多关联查询
下一篇 → 延迟加载配置
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库