SpringBoot百万级数据分页查询效率飞升

百万级数据自定义分页查询效率飞升

一.慢的原因?

offset+limit方式的分页查询,当数据表超过100w条记录,性能会很差。
主要原因是offset limit的分页方式是从头开始查询,然后舍弃前offset个记录,所以offset偏移量越大,查询速度越慢。
mybatis-plus 的分页插件也许存在这个问题。

二.如何优化?

方式1:基于索引再排序,利用MySQL支持ORDER操作可以利用索引快速定位部分元组,避免全表扫描
#大于上一页最后一个id
SELECT * FROM table WHERE id>=10000 ORDER BY id ASC LIMIT 0,20
方式2:利用子查询/连接+索引快速定位元组的位置,然后再读取元组
SELECT a.* FROM table a JOIN (select id from table limit 100000, 20) b ON a.id = b.id

三.上代码(博主用的方式2)

1.自定义Page
@Data
public class MyPage<T> implements Serializable {
    
    
    private List<T> records; //结果集
    private Integer total; //符合条件总条数
    private Integer size; //每页记录数
    private Integer current; //当前处于第几页
    private List<T> orders;
    private final boolean optimizeCountSql = true;
    private final boolean hitCount = false;
    private Integer countId;
    private Integer maxLimit;
    private final boolean searchCount = true;
    private Integer pages; // 符合条件总页数
}
2.ServiceImpl 实现类
public MyPage<XXXPageVo> getPageXXX(XXXDto dto) {
    
    
        QueryWrapper<XXX> qw = new QueryWrapper<>();
        qw.lambda() //以下条件是我的业务条件,自行变更自己业务条件
                .eq(ObjectUtils.isNotEmpty(dto.getOrderId()),XXX::getOrderId,dto.getOrderId())
                .eq(ObjectUtils.isNotEmpty(dto.getOrderNo()) && dto.getOrderNo().length() == 14,XXX::getNewSysOrderNo,dto.getOrderNo())
                .eq(ObjectUtils.isNotEmpty(dto.getOrderNo()) && dto.getOrderNo().length() > 14,XXX::getOrderNo,dto.getOrderNo())
                .eq(ObjectUtils.isNotEmpty(dto.getMainDriverId()),XXX::getMainDriverId,dto.getMainDriverId())
                .like(ObjectUtils.isNotEmpty(dto.getMainDriverName()),XXX::getMainDriverName,dto.getMainDriverName())
                .like(ObjectUtils.isNotEmpty(dto.getMainDriverVehicleNo()),XXX::getMainDriverVehicleNo,dto.getMainDriverVehicleNo())
                .eq(ObjectUtils.isNotEmpty(dto.getKmDriverId()),XXX::getKmDriverId,dto.getKmDriverId())
                .like(ObjectUtils.isNotEmpty(dto.getKmDriverName()),XXX::getKmDriverName,dto.getKmDriverName())
                .like(ObjectUtils.isNotEmpty(dto.getKmDriverVehicleNo()),XXX::getKmDriverVehicleNo,dto.getKmDriverVehicleNo())
                .eq(ObjectUtils.isNotEmpty(dto.getLineGroupId()),XXX::getLineGroupId,dto.getLineGroupId())
                .eq(ObjectUtils.isNotEmpty(dto.getTotalLineGroupId()),XXX::getTotalLineGroupId,dto.getTotalLineGroupId())
                .eq(ObjectUtils.isNotEmpty(dto.getPassengerMobile()),XXX::getPassengerMobile,dto.getPassengerMobile())
                .eq(ObjectUtils.isNotEmpty(dto.getKmDriverScore()),XXX::getKmDriverScore,dto.getKmDriverScore())
                .eq(ObjectUtils.isNotEmpty(dto.getZtDriverScore()),XXX::getZtDriverScore,dto.getZtDriverScore())
                .eq(ObjectUtils.isNotEmpty(dto.getUid()),XXX::getUid,dto.getUid())
                .eq(ObjectUtils.isNotEmpty(dto.getStatus()),XXX::getStatus,dto.getStatus());
        if(ObjectUtils.isNotEmpty(dto.getStartArrangeRunTime()) && ObjectUtils.isNotEmpty(dto.getEndArrangeRunTime())){
    
    
            long sl = dto.getStartArrangeRunTime().toInstant(ZoneOffset.ofHours(8)).toEpochMilli()/1000;
            long el = dto.getEndArrangeRunTime().toInstant(ZoneOffset.ofHours(8)).toEpochMilli()/1000;
            qw.lambda().between(XXX::getArrangeRunTime,sl,el);
        }
        if(ObjectUtils.isNotEmpty(dto.getStartUpdateTime()) && ObjectUtils.isNotEmpty(dto.getEndUpdateTime())){
    
    
            long slu = dto.getStartUpdateTime().toInstant(ZoneOffset.ofHours(8)).toEpochMilli()/1000;
            long elu = dto.getEndUpdateTime().toInstant(ZoneOffset.ofHours(8)).toEpochMilli()/1000;
            qw.lambda().between(XXX::getUpdateTime,slu,elu);
        }
        // CurrentSize 自定义字段 例如: 现在分页处于第3页,每页条数是10条,那么第四页需要从
        // 第31条开始查询,抛弃前面30条数据,也就有了下面的这行代码 
        dto.setCurrentSize(dto.getCurrent() * dto.getSize() - dto.getSize());
        Integer total = baseMapper.selectCount(qw); // 关键点(查询出符合条件的总条数)
        List<XXXPageVo> resList = baseMapper.pageComments(dto);
        //以下是自定义Page拼装
        MyPage<XXXPageVo> page = new MyPage<>();
        int pages = (int) Math.ceil((double) total / dto.getSize());
        page.setTotal(total);
        page.setPages(pages);
        page.setRecords(resList);
        page.setSize(dto.getSize());
        page.setCurrent(dto.getCurrent());
        return page;
    }

3.Mapper.xml

<select id="pageComments" parameterType="包名.XXXDTO" resultType="包名.XXXPageVO">
        SELECT
        a.id,
        uniacid,
        order_no,
        order_id,
        total_line_group_id,
        total_line_group_name,
        line_group_id,
        zt_driver_label_ids,
        zt_driver_score,
        zt_driver_other_comment,
        zt_driver_label_str,
        main_driver_vehicle_no,
        km_driver_label_ids,
        km_driver_score,
        km_driver_label_str,
        description,
        main_driver_id,
        km_driver_other_comment,
        main_driver_name,
        km_driver_id,
        km_driver_name,
        km_driver_vehicle_no,
        other_comment_content,
        order_name,
        from_unixtime( arrange_run_time, '%Y-%m-%d %H:%i:%s' ) AS arrange_run_time,
        passenger_name,
        passenger_mobile,
        uid,
        wx_name,
        new_sys_order_no,
        recovery_time,
        from_unixtime( update_time, '%Y-%m-%d %H:%i:%s' ) AS update_time,
        update_user_id,
        update_user_name,
        is_del,
        STATUS,
        from_type
        FROM
        table a
        INNER JOIN
        (
        SELECT
        id
        FROM
        table
        where
        is_del = 0
        <if test="XXXDTO.orderId != null and XXXDTO.orderId != '' ">
            AND order_id = #{XXXDTO.orderId}
        </if>
        <if test="XXXDTO.orderNo != null and XXXDTO.orderNo != '' and XXXDTO.orderNo.length == 14 ">
            AND new_sys_order_no = #{XXXDTO.orderNo}
        </if>
        <if test="XXXDTO.orderNo != null and XXXDTO.orderNo != '' and XXXDTO.orderNo.length &gt; 14 ">
            AND order_no = #{XXXDTO.orderNo}
        </if>
        <if test="XXXDTO.mainDriverId != null and XXXDTO.mainDriverId != '' ">
            AND main_driver_id = #{XXXDTO.mainDriverId}
        </if>
        <if test="XXXDTO.mainDriverName != null and XXXDTO.mainDriverName != '' ">
            AND main_driver_name like CONCAT('%',CONCAT(#{XXXDTO.mainDriverName},'%'))
        </if>
        <if test="XXXDTO.mainDriverVehicleNo != null and XXXDTO.mainDriverVehicleNo != '' ">
            AND main_driver_vehicle_no like CONCAT('%',CONCAT(#{XXXDTO.mainDriverVehicleNo},'%'))
        </if>
        <if test="XXXDTO.kmDriverId != null and XXXDTO.kmDriverId != '' ">
            AND km_driver_id = #{XXXDTO.kmDriverId}
        </if>
        <if test="XXXDTO.kmDriverName != null and XXXDTO.kmDriverName != '' ">
            AND km_driver_name like CONCAT('%',CONCAT(#{XXXDTO.kmDriverName},'%'))
        </if>
        <if test="XXXDTO.kmDriverVehicleNo != null and XXXDTO.kmDriverVehicleNo != '' ">
            AND km_driver_vehicle_no like CONCAT('%',CONCAT(#{XXXDTO.kmDriverVehicleNo},'%'))
        </if>
        <if test="XXXDTO.lineGroupId != null and XXXDTO.lineGroupId != '' ">
            AND line_group_id = #{XXXDTO.lineGroupId}
        </if>
        <if test="XXXDTO.totalLineGroupId != null and XXXDTO.totalLineGroupId != '' ">
            AND total_line_group_id = #{XXXDTO.totalLineGroupId}
        </if>
        <if test="XXXDTO.passengerMobile != null and XXXDTO.passengerMobile != '' ">
            AND passenger_mobile = #{XXXDTO.passengerMobile}
        </if>
        <if test="XXXDTO.kmDriverScore != null and XXXDTO.kmDriverScore != '' ">
            AND km_driver_score = #{XXXDTO.kmDriverScore}
        </if>
        <if test="XXXDTO.ztDriverScore != null and XXXDTO.ztDriverScore != '' ">
            AND zt_driver_score = #{XXXDTO.ztDriverScore}
        </if>
        <if test="XXXDTO.uid != null and XXXDTO.uid != '' ">
            AND uid = #{XXXDTO.uid}
        </if>
        <if test="XXXDTO.status != null and XXXDTO.status != '' ">
            AND status = #{XXXDTO.status}
        </if>
        <if test="XXXDTO.startArrangeRunTime != null and XXXDTO.endArrangeRunTime != null ">
            AND arrange_run_time BETWEEN unix_timestamp(#{XXXDTO.startArrangeRunTime}) AND unix_timestamp(#{XXXDTO.endArrangeRunTime})
        </if>
        <if test="XXXDTO.startUpdateTime != null and XXXDTO.endUpdateTime != null">
            AND update_time BETWEEN unix_timestamp(#{XXXDTO.startUpdateTime}) AND unix_timestamp(#{XXXDTO.endUpdateTime})
        </if>
        ORDER BY update_time DESC
        LIMIT #{XXXDTO.currentSize},
        #{XXXDTO.size}
        ) b on a.id = b.id
    </select>

关于效果图也就不截图了,大多数博客大概也用这两种方式,博主只是整合了一下逻辑,避一避坑,并用于线上项目,效率是真的提升了几十个倍,如果再加入数据库索引辅助的话会更快

猜你喜欢

转载自blog.csdn.net/hsadfdsahfdsgfds/article/details/127474046
今日推荐