• 深度翻页导出导致慢SQL,mysqlCPU飙升优化方案


    慢SQL原因分析:

    1.深度翻页

    2.多表JOIN

    3. 大IN

    4. id倒排序

    本文针对深度翻页的优化进行探讨

    方案1: 

    limit   offset, pageSize的方式改成 id > xx limit pageSize.

    这样能走Id索引,提高速度。

    缺点:不能使用多线程,入参ID从上页结果。

    方案2:

    基于 方案1再优化, 将limit   offset, pageSize 的方式改成 id > startId and id< endId .

    一次性查出符合条件的ID范围,然后切分ID范围进行查询。(可分实际ID划分,或逻辑范围划分)

    优点:  能用多线程并发查询。

    缺点:逻辑范围划分有的id范围可能无数据,进行无效查询。

    方案3:

    终极方案:设置fetchSize,思想是 一次查询在Mysql侧缓冲全量数据,程序侧通过游标cursor批量读取数据,通过回调函数resulthandler处理数据。

    优点: 不用多次和Mysql查询,一次查询多次读取数据。回调里可以使用多线程操作数据。

    缺点: Mysql要缓冲全量数据,内存飙升

    方案二步骤:

    (1) 查询 对应表的ID范围,COUNT条数

    (2) 根据count条数,和每页数量,计算页数,根据页数 和 ID范围进行ID范围切分。

    (3)根据ID范围,发起多线程并发查询。

    其中具体核心逻辑代码:

    ID范围查询

    1. <!-- 统计分页查询总条数 -->
    2. <select id="findIdRange" resultType="com.xyy.ms.export.core.erpreport.dto.ExportIdRangeDTO">
    3. select
    4. min(b.id) as minId, max(b.id) as maxId, count(1) as count
    5. from storage_batchnum b
    6. <include refid="batchNumExportWhere"></include>
    7. </select>

    ID切分逻辑:

    1. package com.xyy.ms.export.core.erpreport.dto;
    2. import lombok.AllArgsConstructor;
    3. import lombok.Getter;
    4. import lombok.Setter;
    5. import lombok.ToString;
    6. import java.io.Serializable;
    7. import java.util.ArrayList;
    8. import java.util.List;
    9. /**
    10. * @author stivenjin
    11. * @version 1.0
    12. * @description 说明: 取表中最小和最大ID, 用ID翻页查询,避免深度翻页(批号库存翻页导出)
    13. * 翻页优化步骤:
    14. * 1:根据ID范围,进行切分组
    15. * 2:用每组 的边界值进行id范围翻页查询。
    16. * @date 2023/9/1 18:10
    17. */
    18. @Getter
    19. @Setter
    20. @ToString
    21. @AllArgsConstructor
    22. public class ExportIdRangeDTO implements Serializable {
    23. /**
    24. * 最小ID
    25. */
    26. private int minId = 0;
    27. /**
    28. * 最大ID
    29. */
    30. private int maxId = 0;
    31. /**
    32. * 总条数
    33. */
    34. private long count = 0;
    35. public boolean isValid() {
    36. return minId > 0 && maxId > 0;
    37. }
    38. /**
    39. * 按页数分隔ID范围
    40. * @param pageCount
    41. * @return
    42. */
    43. public List splitByPageCount(int pageCount) {
    44. List splitList = new ArrayList();
    45. int startId = minId;
    46. int endId = maxId;
    47. int pageSize = (int)Math.ceil((Double.valueOf(maxId) - Double.valueOf(minId)) / pageCount);
    48. System.out.println("pageSize:" + pageSize + ",pageCount:" + pageCount);
    49. int tmp = endId;
    50. for(int i = 1 ;i<=pageCount;i++){
    51. if(startId <= tmp){
    52. if(startId + pageSize <= tmp){
    53. endId = startId + pageSize ;
    54. }else{
    55. endId = tmp;
    56. }
    57. }else{
    58. break;
    59. }
    60. //System.out.println("循环调用:" + startId + " : " + endId);
    61. splitList.add(new ExportIdRangeDTO(startId, endId, 0));
    62. if(endId <= tmp){
    63. startId = endId +1;
    64. }
    65. }
    66. return splitList;
    67. }
    68. public static void main(String[] args) {
    69. ExportIdRangeDTO dto = new ExportIdRangeDTO(100,823540, 0);
    70. dto.splitByPageCount(10);
    71. System.out.println("切分一片原始:" + dto.getMinId() + " : " + dto.getMaxId());
    72. }
    73. }
    
       and b.id >= #{minId} and b.id <= #{maxId}
    

    按ID范围切分后,可用多线程并发查询导出

    taskExecutor.submit

    1. // 增加顺序按起点ID导出模式,避免深度翻页慢SQL(之前是多线程并发深度翻页查MYSQL,mysql cpu飙升)
    2. if (batchNumExportUseId) {
    3. ExportIdRangeDTO idRangeRes = exportStorageBatchNumApi.findIdRange(params);
    4. logger.info(" taskId [{}] 开始-异步顺序导出,idRange={}",taskId, JSON.toJSONString(idRangeRes));
    5. if (idRangeRes != null && idRangeRes.isValid()) {
    6. paramsObject.put("pageSize", StorageWebConstant.PURCHASE_CALL_PAGESIZE);
    7. int pageCnt = (int)(idRangeRes.getCount()/StorageWebConstant.PURCHASE_CALL_PAGESIZE);
    8. pageCnt = pageCnt + (idRangeRes.getCount()%StorageWebConstant.PURCHASE_CALL_PAGESIZE == 0 ? 0:1);
    9. List idRangeList = idRangeRes.splitByPageCount(pageCnt);
    10. AtomicInteger pageNum = new AtomicInteger(0);
    11. for (ExportIdRangeDTO idRange : idRangeList) {
    12. int pn = pageNum.incrementAndGet();
    13. Map exportParamMap = new HashMap<>();
    14. exportParamMap.putAll(paramsObject);
    15. exportParamMap.put("pageNum", pn);
    16. exportParamMap.put("minId", idRange.getMinId());
    17. exportParamMap.put("maxId", idRange.getMaxId());
    18. logger.info("## taskId [" + taskId + "]开始导出,第 " + pn + " 页 {}-{}", idRange.getMaxId(), idRange.getMaxId());
    19. exportMap.putIfAbsent(pn, taskExecutor.submit(() -> storageReportService.listStorageBatchNumReportView(exportParamMap)));
    20. }
    21. for (int i = 1; i <= pageNum.get(); i++) {
    22. List list = exportMap.get(i).get().getList();
    23. ExportExcelUtil.insertDataToExcel(work, colName, list, line, true);
    24. line = line + list.size();
    25. }
    26. }
    27. }

  • 相关阅读:
    H3C WX2510h无线控制器如何网关式部署无线网络
    Vue整合
    Caffe: Convolutional Architecture for Fast Feature Embedding
    【多线程与高并发】从一则招聘信息进入多线程的世界
    C Primer Plus(6) 中文版 第3章 数据和C 3.1 示例程序
    MCE | 打破 Western Blot 玄学操作
    RTSP协议学习
    vue 打包配置
    代码随想录算法训练营day56 | 300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组
    想买个深度学习的算力设备,TOPs和TFLOPs 啥啥分不清
  • 原文地址:https://blog.csdn.net/Jinliang_890905/article/details/132668549