我们在写代码的时候非常忌讳出现n+1次查询,这就意味的你的循环有多少次,就会查询多少次数据库,这是很恐怖的场景。
因为每次服务调用mysql查询的时候,都是一件很耗费性能的操作,下面我们举个例子,来说说n+1的触发场景及解决方案。
需要查询指定用户的订单详细信息,详细信息不仅仅包含订单本身的信息,还包含其它信息。这个时候童鞋们往往会采用,如下所示的方式进行数据获取。
/**
* 订单mapper
*/
private OrderMapper orderMapper;
/**
* 订单商品mapper
*/
private OrderFeeMapper orderFeeMapper;
/**
* 查询用户id指定的所有订单列表信息
* @param userId
* @return
*/
public List<OrderDetail> getOrderDetailList(int userId) {
// 查询订单列表数据
List<Order> orderList = orderMapper.getOrders(userId);
List<OrderDetail> orderDetailList = new ArrayList<>();
for (Order order : orderList) {
OrderDetail orderDetail =new OrderDetail();
// 查询订单对应费用信息
OrderFee orderFee = orderFeeMapper.getOrderFeeDetail(order.getOrderId());
orderDetail.setOrderFee(orderFee);
// 添加到集合中
orderDetailList.add(orderDetail);
}
return orderDetailList;
}
如果这个用户订单量少还好,一旦这个用户订单量超级大,这个操作的响应时间将会非常长,长到你无法忍受的地步,那我们要怎么进行优化呢?
我们可以将n次查询的条件添加到一个集合中,然后通过in语句一次性查询出我们需要的数据,这样就可以避免n+1次查询的出现,可以大大提高我们的执行效率,代码如下所示:
/**
* 订单mapper
*/
private OrderMapper orderMapper;
/**
* 订单商品mapper
*/
private OrderFeeMapper orderFeeMapper;
/**
* 查询用户id指定的所有订单列表信息
* @param userId
* @return
*/
public List<OrderDetail> getOrderDetailList(int userId) {
// 查询订单列表数据
List<Order> orderList = orderMapper.getOrders(userId);
List<OrderDetail> orderDetailList = new ArrayList<>();
List<String> orderIdList = new ArrayList<>();
for (Order order : orderList) {
OrderDetail orderDetail =new OrderDetail();
// 添加订单到集合中
orderIdList.add(order.getOrderId());
// 添加到集合中
orderDetailList.add(orderDetail);
}
List<OrderFee> orderFeeList = orderFeeMapper.getOrderFeeList(orderIdList);
// 递归将orderFeeList中费用信息设置到对应订单的orderDetail对象中(具体代码省略)
setOrderDetail(orderFeeList,orderDetailList);
return orderDetailList;
}
童鞋们可能会问为啥不采用mysql连接查询,一下子将相关表数据一起查询出来。这边主要出于如下考虑:
笛卡儿积
连接查询其实就是笛卡尔积的应用,一张表的查询操作可能会很快,但是多张表联查就会非常慢,因为他们的数据量是n*m,所以有时候采用连接查询,还不如分成多次查询来的快。
分库分表
如果系统的数据库采用的是分库分表,这个时候有些表是不能够进行连接查询,我们只能分多次查询,然后组装到一起。
数据来源不一致
如果订单的数据是从第三方接口获取的,那我们自然没办法进行连表查询。
我们写代码的时候一定要特别注意n+1查询出现,循环体内要多检查几遍,是否有子查询的出现。
童鞋们要记住,每一种模式都存在一定的缺陷,数据量不一样,模式的执行效率天差地别。童鞋们有空的话可以思考如下问题: