大数据分页查询 or 导出 慢sql治理

  • 背景
    • 缺陷
    • 要求
  • 优化方案
    • 适用场景
    • 方案优点
    • 方案缺点
    • 时间拆分如处理分页查询问题
    • 方案说明
  • 使用说明
    • 分页查询工具
    • 时间拆分工具

背景

当前日增数据量将近千万,条件查询虽然做了联合索引,但扫描行数仍有30w左右,会出现超一秒的慢sql

缺陷

原因分析
1、联合索引首个未添加分库分表键
2、当前分库分表键是根据业务选择,数据分布不均匀
3、根据时间查询,跨度较大,扫描行多

要求

1、由于数据量太大,添加索引困难,并且查询场景为web页面,频率较低,为不影响线上业务,不去修改索引(存在4种不同时间查询,修改要加4个)
2、由于业务需要,时间查询跨度为7天,为不影响使用,不缩短查询时间范围

优化方案

1、时间拆分查询
主要的优化方案,也是接下来要讲的方案
2、count 加缓存
页面分页查询,多次count影响性能,虽然不能解决慢sql问题,但对查询性能有部分提升
3、部分走强制索引
页面允许使用单号查询,单号为唯一键,但mysql查询仍然走了时间索引,这部分查询强制走单号索引

适用场景

1、大数据量导出。
2、查询。注:对查询速度要求不高,允许损耗部分时间以达到完全去除慢sql查询

方案优点

页面是分页查询,条数最大50条,实际总数据量2-3万,用户只会查询前几页,根据时间拆分查询时,查询第1天数据就完全满足,相对原来只是多了一次count,并且扫描行大大降低,对查询时间影响较小

方案缺点

原来一次查询数据库,优化后需要多次count和数据查询,增加了查询时间
注:实际发现时间确实有增加,但是增加不多

时间拆分如处理分页查询问题

1、在对count进行拆分时,只需要对时间按天 or 小时拆分,对拆分后多次count,求和即可,但是页面的要求是分页查询,如何实现时间拆分的同时,可以实现分页查询?

方案说明

由于数据查询根据时间按天拆分,那么拆分后每次查询需要对sql中偏移量和查询条数进行重新计算

例如: 查询签收时间在6天内的数据,分页查询,每页50条,查询第n页
需要查询的数据为 select * from [表A] [条件] limit 50(n-1),50

要求:查询第3页数据

如果查询的数据分布如下 day1下符合数据30条,day2下100条…

思路:从day1开始获取数据,到day6结束,如果提前获取到数据,则直接返回
n=3时,即第3页数据查询 limit 100,50 查询的数据为第101条开始,到150条结束 [101,150]
第一次count ,得到day1 数据 30条,数据范围ab [1,30],不符合
第二次count ,得到day2 数据 100条,数据范围cd [31,130],部分符合,符合数据范围ef[101,130] ,
根据符合范围,计算偏移量和查询条数
需要查询的数据为 select * from [表A] [条件 时间=day2] limit A,B
A=e-c=101-31=70
B= 130 - A = 30
得到查询数据30条,说明需要继续查询20条
第三次count ,得到day3 数据 30条,数据范围 gh[131,160],部分符合,符合数据范围ij[131,150] ,
根据符合范围,计算偏移量和查询条数
需要查询的数据为 select * from [表A] [条件 时间=day3] limit A,B
A=i-g=131-131=0
B= 150 - A = 20
得到查询数据20条,加上day2查询的30条,共50条,查询结束 END

使用说明

// e是查询对象
//初始化
PageQueryUtil<WaybillWholeQueryDTO,WaybillWholeDO> pageQueryUtil = new PageQueryUtil();pageQueryUtil.init(query.getOffset(),query.getPageSize());//时间拆分,获取map,开始时间和结束时间
Map<LocalDateTime, LocalDateTime>  dateTimeMap = LocalDateTimeUtil.splitByDay(query.getCollectTimeBegin(), query.getCollectTimeEnd());
//根据原来查询对象e,把查询时间修改,其他不该,获取多个e查询对象
List<E> list = build(dateTimeMap,e);
for (E queryObj : list) {//查询某天总页数
Long count = waybillWholeDAO.count(e);
//判断是否需要查询,如果要,会自动设置pageSize和offset
Boolean continueQuery = pageQueryUtil.isContinueQuery(count, e);
//判断是否需要查询
if (continueQuery){List<WaybillWholeDO> waybillWholeDOS=waybillWholeDAO.queryWithCollectTimeIndex(e);pageQueryUtil.addAll(waybillWholeDOS);}    }

分页查询工具

@Data
public class PageQueryUtil<T extends Page,V> {//开启使用Boolean open = Boolean.FALSE;//查询偏移量Integer originalOffset;//查询条数Integer originalPageSize;//累计数Integer cumulativeCount = 0;//查询数据范围起始值Integer queryStart = 1;//是否继续查询Boolean continueQuery = Boolean.TRUE;//是否最后一次查询Boolean isLastQuery = Boolean.FALSE;List<V> data = new ArrayList<>();public void init(Integer originalOffset,Integer originalPageSize){this.originalOffset = originalOffset;this.originalPageSize = originalPageSize;this.open = true;}public void addAll(List<V> data){this.data.addAll(data);}public Boolean isContinueQuery(Long count,T query){if (!open){throw new RuntimeException("not pen page query");}if (isLastQuery){return Boolean.FALSE;}//需要查询数据范围为集合 ab [a,b]int a = originalOffset + 1;int b = originalOffset + originalPageSize;//本次查询数据范围为集合 cd [c,d]int c = queryStart;int d = queryStart + count.intValue() - 1;//下个查询数据集合开始值queryStart = d + 1;// 本次是否需要查询continueQuery = !(d < a || c > b);if (!continueQuery) {return Boolean.FALSE;}//本次是否需要查询数据  求 cd 与 ab交集 ef [] ,得int e = c <= a ? a : c;int f = d >= b ? b : d;//本次查询数int queryCount = f - e + 1;//剩余需要查询数int needQueryCount =  originalPageSize - cumulativeCount;if (needQueryCount < queryCount){queryCount = needQueryCount;}//累计数添加cumulativeCount = cumulativeCount + queryCount;//是否最后一次查询isLastQuery = cumulativeCount == originalPageSize;//设置查询范围query.setOffset(e - c);query.setPageSize(queryCount);query.setOpen(true);return continueQuery;}
}

时间拆分工具

public class LocalDateTimeUtil {private static final String OFFSET_ID = "+8";private static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";public static final DateTimeFormatter YYYY_MM_DD_COMPACT_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");/*** toEpochMilli** @param localDateTime* @return*/public static long toEpochMilli(LocalDateTime localDateTime) {if (localDateTime == null) {return 0L;}return localDateTime.toInstant(ZoneOffset.of(OFFSET_ID)).toEpochMilli();}/*** format by default pattern** @param localDateTime* @return*/public static String format(LocalDateTime localDateTime) {return format(localDateTime, YYYY_MM_DD_HH_MM_SS);}/*** format by pattern** @param localDateTime* @param pattern* @return*/public static String format(LocalDateTime localDateTime, String pattern) {if (localDateTime == null || StringUtils.isEmpty(pattern)) {return "";}DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);return localDateTime.format(formatter);}/*** format by pattern** @param localDateTime*/public static String format(LocalDateTime localDateTime, DateTimeFormatter formatter) {if (localDateTime == null) {return "";}return localDateTime.format(formatter);}/*** 指定日期当天开始时间* @param localDateTime* @return*/public static LocalDateTime getStartTime(LocalDateTime localDateTime){return LocalDateTime.of(localDateTime.toLocalDate(), LocalTime.of(0, 0));}/*** 指定日期当天结束时间* @param localDateTime* @return*/public static LocalDateTime getEndTime(LocalDateTime localDateTime){return LocalDateTime.of(localDateTime.toLocalDate(), LocalTime.of(23, 59, 59, 999999999));}/*** 指定时间** @param* @return*/public static LocalDateTime getEndTime(String time, String pattern){DateTimeFormatter df = DateTimeFormatter.ofPattern(pattern);return LocalDateTime.parse(time,df);}/*** 按天切割** @param start 开始时间* @param end   结束时间* @return*/public static Map<LocalDateTime, LocalDateTime> splitByDay(LocalDateTime start, LocalDateTime end) {if (start == null || end == null || start.isAfter(end)) {return null;}Map<LocalDateTime, LocalDateTime> map = new TreeMap<>();int days = Period.between(start.toLocalDate(), end.toLocalDate()).getDays();if (days == 0) {map.put(start, end);return map;}LocalDateTime currentDay = start;for (int i = 1; i <= days; i++) {map.put(currentDay, LocalDateTimeUtil.getEndTime(currentDay));currentDay = LocalDateTimeUtil.getStartTime(currentDay).plusDays(1);}map.put(LocalDateTimeUtil.getStartTime(currentDay), end);return map;}public static void main(String[] args) {LocalDateTime start = LocalDateTime.now();LocalDateTime end = LocalDateTime.now().plusMinutes(1);System.out.println(splitByDay(start, end));}}

大数据分页查询 or 导出 慢sql治理相关推荐

  1. 高并发的大数据量查询导致系统频繁死机

    我们的大数据量查询是数据库分页的, 但是导出和打印功能是基于全部数据的. 系统投入使用后,对于导出和打印功能的使用远远要高于我们的预期. 而我们的系统的硬件设备是有限的 不能再升级了. 抓取内存大对象 ...

  2. 【MySQL Tips】偏移量大的分页查询LIMIT子句的优化方法

    SQL优化是要看执行计划分析,并做基准测试的. 前言 MySQL官方关于LIMIT子句的优化建议在之前的文章中写过,链接如下: 8.2.19 LIMIT查询优化.note [MySQL 8翻译]8.2 ...

  3. 大数据量查询:流式查询与游标查询

    最近在做一个计算相关的功能,大体就是有很多条SQL,每条SQL都涉及复杂地运算,最后要将所有计算结果进行合并分析.经初步测试,每个SQL起码会查出几十万条记录,我们现在有毛毛多的这种SQL. 最大的问 ...

  4. 如何选择大数据存储查询引擎

    目录: 1.从需求说起 2.救星OlAP 3.新的问题,高并发 随着大数据技术的成熟,涌现了非常多的成熟框架和技术,在大数据存储查询引擎方面也有非常多的优秀产品.为什么出现这么多的优秀产品,为什么不是 ...

  5. 客户端如何通过咏南中间件调用存储过程和数据分页查询和文件传输的演示

    客户端如何通过咏南中间件调用存储过程和数据分页查询和文件传输的演示 演示使用MSSQL 2000的存储过程,其它类型的数据库的存储过程的语法是有所不同的. 1)MSSQL2000通用数据分页存储过程, ...

  6. 【阿里内部应用】基于Blink为新商业调控打造实时大数据交互查询服务

    基于Blink为新商业调控打造实时大数据交互查询服务 案例与解决方案汇总页: 阿里云实时计算产品案例&解决方案汇总 从IT到DT.从电商到新商业,阿里巴巴的每个细胞都存在大数据的DNA,如何挖 ...

  7. Mybatis最入门---分页查询(逻辑分页与SQL语句分页)

    [一步是咫尺,一步即天涯] 到目前为止,我们介绍的Mybatis种种查询都是一次性的查询出所有结果并返回给上层.但是,在实际开发过程中,在大量数据存在的情况下,是很少这么做的.本文,我们将从逻辑分页, ...

  8. 华为大数据战略_华为大数据开源战略部部长陈亮 - Apache CarbonData,实现大数据即席查询秒级响应...

    1.实现大数据即席查询秒级响应 2.Liang Chen / 陈 亮 华为大数据开源开发部Leader Apache CarbonData PMC & CommitterEmail:chenl ...

  9. 易观CTO郭炜:如何构建企业级大数据Ad-hoc查询引擎

    凭借多年大数据平台建设经验,易观 CTO郭炜为大家分享了易观在大数据实时查询引擎建设过程所获经验与挑战,以及大数据人员如何快速建立自己的大数据查询引擎套件,让自己的数据人员不再是"表哥表妹& ...

最新文章

  1. Android关于Theme.AppCompat相关问题的深入分析
  2. QT的QGeoAreaMonitorSource类的使用
  3. 使用IBM Blockchain Platform extension开发你的第一个fabric智能合约
  4. 077 Apache的HBase与cdh的hue集成(不建议不同版本之间的集成)
  5. postgresql点云las_点云模型_点云模型_模型_时空数据库_PolarDB PostgreSQL 云原生数据库 - 阿里云...
  6. spring boot高性能实现二维码扫码登录(中)——Redis版
  7. 《极客与团队》一第二章 培养出色的团队文化
  8. 天坑-安装salt-api安装的正确姿势
  9. bottleneck resnet网络_Detection学习之四-利用pytorch实现resnet
  10. android自定义viewgroup实现等分格子布局
  11. python生成姓名,python生成随机姓名
  12. TLQ的安装路径不存在或不正确
  13. Bringing up interface eth0: Device eth0 does not seem to be present,delaying initialization
  14. HDMI/DVI分配器芯片
  15. ppt加载html5,当PPT遇见H5,这才是真爱!
  16. [BJDCTF2020]Mark loves cat 1
  17. Linux——网络桥接
  18. vim的复制、粘贴操作
  19. matlab文件批量重命名并编号排序
  20. JVM设置对象直接进入年老代

热门文章

  1. STM32 ---deley延时两行代码实现【为方便移植文件】
  2. 数据库运维实操优质文章分享(含Oracle、MySQL等) | 2023年5月刊
  3. TeXstudio的NOMENCLATURE
  4. 满分室间质评之GATK Somatic SNV+Indels+CNV+SV(上)
  5. SNV分支和合并、切换分支
  6. 【笔试题】维克多博士创造了一个裂变反应堆
  7. 鸿蒙的内核版本为什么是安卓,为什么说安卓已经落后于鸿蒙OS?
  8. 看得多 记得少 亡羊补牢 不晚不晚
  9. pythondcnda算法聚类_ML-hand/5kmeans聚类.ipynb at master · Briareox/ML-hand · GitHub
  10. 几个很特别的音乐搜索网站