Mybatis嵌套查询的N+1问题与内存性能权衡

/ SQL / 1 条评论 / 6540浏览

认知

拿本地做性能测试的同学,右上角,再见不送。

为啥再见不送?你以为网络传输不要钱的么?

情景

一个工程师(user)属于一个部门(department,后面简称dept),A是CMS部门的工程师,B是用户中心的工程师,C是订单组的工程师,D是运维部门的工程师。

简介

在orm框架中,都可以设置关联对象,即一个类型中包含另一个类型的成员属性,比如user对象关联dept 假如查询出n个user,那么需要做n次查询dept,查询user是一次select,查询user关联的 dept,是n次,所以是n+1问题,其实叫1+n更为合理一些。

mybatis配置

UserMapper.xml

<resultMap id="BaseResultMap" type="testmaven.entity.User">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="name" jdbcType="VARCHAR" property="name" />
    <result column="age" jdbcType="INTEGER" property="age" />
    <result column="dept_id" jdbcType="INTEGER" property="deptId" />
    <association property="dept" column="dept_id" fetchType="eager" select="mapper.DeptMapper.selectByPrimaryKey" ></association>
  </resultMap>

DeptMapper.xml

 <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
    select 
    <include refid="Base_Column_List" />
    from dept
    where id = #{id,jdbcType=INTEGER}
  </select>

通过UserMapper.xml 可以看出来user通过association中的dept_id关联了dept

List<User> list = userMapper.selectByExample(null);

打印jdbc log我们能看到,查询到4个user,然后执行了4次查询dept

请输入图片描述

查询主数据,是1次查询,查询出n条记录;根据这n条主记录,查询从记录,共需要n次,所以叫数据库1+n问题; select语句的数目太多,需要频繁的访问数据库,会在网络传输上浪费大量资源,同时会影响检索性能。 如果需要查询n个user对象,那么必须执行n+1次select查询语句。

5条select语句完全可以通过以下1条select语句来完成:

select * from user 
left outer join dept
on user.dept_id=dept.id

以上select语句使用了SQL的左外连接查询功能,能够在一条select语句中查询出user表的所有记录,以及匹配的dept表的记录。

新问题

在应用逻辑只需要访问user对象,而不需要访问dept对象的场合,加载dept对象完全是多余的操作,这些多余的dept对象白白浪费了许多内存空间。

  1. 解决方案一:配置两套resultMap,根据不同需求使用不同的resultMap。一个包含dept,一个不包含。
  2. 解决方案二:我们设置user的mapper中association元素中的fetchType fetchType=lazy

结论

方案二不推荐,如果全都使用,依旧不能避免N+1的问题。 方案一可以有效解决N+1问题和内存占用过大问题。

作者:LUKENC 注明原创!!

  1. 我想问一下,这个n+1的n控制在多少比较合适,30以下可以吗