什么是N+1问题?
-
你执行了一个单独的 SQL 语句来获取结果的一个列表(就是“+1”)。
-
对列表返回的每条记录,你执行一个 select 查询语句来为每条记录加载详细信息(就是“N”)。
这个问题会导致成百上千的 SQL 语句被执行。有时候,我们不希望产生这样的后果。
什么情况会存在N+1问题?
首先单表查询时不会存在N+1问题的,存在N+1问题的情况就是存在关联嵌套查询的时候。
举个例子,场景:一个电商系统,查询所有的客户信息(Customer)。
假如此时如果我们的客户信息表是与订单信息表关联的。
//客户信息表
public class Customer {
private String customerId;
private String cutomerName;
//订单表,一个客户可能有多个订单
private List<Order> orders;
public String getCustomerId() {
return customerId;
}
public void setCustomerId(String customerId) {
this.customerId = customerId;
}
public String getCutomerName() {
return cutomerName;
}
public void setCutomerName(String cutomerName) {
this.cutomerName = cutomerName;
}
public List<Order> getOrders() {
return orders;
}
public void setOrders(List<Order> orders) {
this.orders = orders;
}
}
//订单表
public class Order {
private String orderId;
private String customerId;
private int num;
private double total;
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getCustomerId() {
return customerId;
}
public void setCustomerId(String customerId) {
this.customerId = customerId;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public double getTotal() {
return total;
}
public void setTotal(double total) {
this.total = total;
}
}
当我们这个时候要查询客户信息时,如果mapper文件写法如下:
<resultMap type="Customers" id="customersResultMap" >
<id property="customerId" column="customer_id" />
<result property="customerName" column="customer_name" javaType="string"/>
<collection property="orders" ofType="Order" select="getCustOrders" column="customerId" />
</resultMap>
-- 查询所有客户信息
<select id="querAllCustomers" parameterType="java.util.HashMap" resultMap="customersResultMap">
select customer_id, customer_name from tb_customers
</select>
-- 查询客户的所有订单
<select id="getCustOrders" resultType="Order">
SELECT * FROM tb_order WHERE customer_id = #{customerId}
</select>
当我们查询客户信息时,系统先调用querAllCustomers()方法,执行如下过程
select customer_id, customer_name from tb_customers 1次
SELECT * FROM tb_order WHERE customer_id = 1
SELECT * FROM tb_order WHERE customer_id = 2
SELECT * FROM tb_order WHERE customer_id = 3
…
SELECT * FROM tb_order WHERE customer_id = n
综上所述即:执行了一个单独的 SQL 语句来获取结果的一个列表,对列表返回的结果,执行一个 select 查询语句来为每条记录加载详细信息。
当客户信息表数据量非常大,每个客户的订单量也非常大的时候,对数据库查询性能消耗可想而知。
解决办法:关联的嵌套结果映射查询,通过SQL语句一次性查询出来。
修改mapper配置文件如下:
<resultMap type="Customers" id="customersResultMap" >
<id property="customerId" column="customer_id" />
<result property="customerName" column="customer_name" javaType="string"/>
<collection property="orders" ofType="Order">
<id column="order_id" property="orderId"/>
<result property="customerId" column="customer_id" javaType="string"/>
<result property="num" column="num" javaType="string"/>
<result property="total" column="total" javaType="string"/>
</collection>
</resultMap>
<select id="querAllCustomers" parameterType="java.util.HashMap" resultMap="customersResultMap">
select cm.customer_id, cm.customer_name
from tb_customers cm
left join tb_order od
on cm.customer_id = od.customer_id
</select>