1. 概览

数据库 Join 真的太香了,但由于各种原因,在实际项目中越来越受局限,只能由开发人员在应用层完成。这种繁琐、无意义的“体力劳动”让我们离“快乐生活”越来越远。

1.1. 背景

不知道什么时候,数据库 join 成为了公认的“性能杀手”,对此,很多公司严厉禁止其使用。上有政策下有对策,你的应对之道是什么?

数据库 Join 退出历史舞台,主要由以下几大推动力:

微服务。微服务要求“数据资产私有化”,也就是说每个服务的数据库是私有资产,不允许其他服务的直接访问;如果需要访问,只能通过服务所提供的接口完成;

分库分表的限制。当数据量超过 MySQL 单实例承载能力时,通常会通过“分库分表”这一技术手段来解决,分库分表后,数据被分散到多个分区中,导致 join 语句失效;

性能瓶颈。在高并发情况下,join 存在一定的性能问题,高并发、高性能端场景不适合使用;

不管原因几何,目前,很多大厂已经将 “禁止join” 列入编码规范,我们该如何面对?

只定规范,不给工具,是一种极度不负责任的表现。

1.1.1. 线上问题跟踪

线上 order/list 接口 tp99 超过 2s,严重影响用户体验,同时还有愈演愈烈之势。通过 Trace 系统,发现一个请求居然存在几百甚至上千次 DB 调用!

第一反应,肯定是在 for 循环中调用了 DB,翻看代码果然如此,代码示例如下:

@Override

public List getByUserId(Long userId) {

    List orders = this.orderRepository.getByUserId(userId);

    return orders.stream()

            .map(order -> convertToOrderDetailVO(order))

            .collect(toList());

}

private OrderDetailVOV1 convertToOrderDetailVO(Order order) {

    OrderVO orderVO = OrderVO.apply(order);

    OrderDetailVOV1 orderDetailVO = new OrderDetailVOV1(orderVO);

    Address address = this.addressRepository.getById(order.getAddressId());

    AddressVO addressVO = AddressVO.apply(address);

    orderDetailVO.setAddress(addressVO);

    User user = this.userRepository.getById(order.getUserId());

    UserVO userVO = UserVO.apply(user);

    orderDetailVO.setUser(userVO);

    Product product = this.productRepository.getById(order.getProductId());

    ProductVO productVO = ProductVO.apply(product);

    orderDetailVO.setProduct(productVO);

    return orderDetailVO;

}

代码非常简单,只做了几件事:

获取用户的 order 信息;

遍历每一个 order,为其装配关联数据;

返回最终结果;

逻辑非常清晰,单请求数据库访问总次数 = 1(获取用户订单)+ N(订单数量) * 3(需要抓取的关联数据)

可见,N(订单数量) * 3(关联数据数量)  是性能的最大杀手,存在严重的读放大效应。不同的用户,订单数量相差巨大,导致该接口性能差距巨大。

1.1.2. 繁琐、无意义的代码

如何应对?第一反应就是 批量获取,然后在内存中完成 Join。这是一个好的方案,但引入了大量繁琐、无意义的代码。

该问题常规解决方案如下:

@Override

public List getByUserId(Long userId) {

    List orders = this.orderRepository.getByUserId(userId);

    List orderDetailVOS = orders.stream()

            .map(order -> new OrderDetailVOV2(OrderVO.apply(order)))

            .collect(toList());

    List userIds = orders.stream()

            .map(Order::getUserId)

            .collect(toList());

    

好文链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: