Java数据库分表与多线程查询结果汇总
今天接到一个需求:要对一个物理分表的逻辑表进行查询统计。而数据库用的是公司自己研发的产品,考虑的到公司产品的特点以及业务的需求,该逻辑表是按年月进行分表的,而非分区。我们来看一下,在按时间段进行查询统计的时候,会有哪些问题:
- 需要对多个表查询,且表个数不确定
- 时间跨度越大,查询等等表个数越多,对应查询时间也会越长
如何解决?一起来看一下
分表与分区
目的
既然谈到数据的分表与分区,那我们来简单了解一下。先说一下分表与分区的目的。我们日常开发中都会经常遇到百万或千万级的数据大表,这些表数据量大,数据增速快,不用太久就会造成在查询或修改数据库数据的时候造成性能低下的问题,联合查询的时候,情况可能更糟。一次有必要对原来的表进行改造设计。这时候数据库分区和分表技术就应运而生了
区别
分表
分表是将一个大表按照一定的规则分解成多张子表,而各个子表存储空间彼此独立。
分区
分区也是按照一定的规则进行数据划分,对各部分数据各自存储,但在处理逻辑上,散列存放的数据还是属于同一张大表。
依赖于数据库实现,对程序屏蔽,减轻程序员编程压力
分表逻辑下的多线程查询与数据汇总
回到文首提到的情况,当前的情况是分表,分表的划分依据是根据年月划分,一个月一张表。意味着当我们要统计跨多个隔离单位的数据进行统计时,要自己去实现的对分散在多个表中数据的查询汇总处理。
通常表名会带有划分依据的信息,比如按年月划分,表名格式一般为
TABLE_NAME_YYYYMM
确定数据表
当前的需求是对一段时间内的数据进行统计,时间单位精确到月份。一次当我们根据服务入参拿到开始月份和结束月份后,要先得到所有涉及的月份。我们可以计算出将所有月份并保存在一个List中,方便我们查询各个表时进行表名的拼接。代码实现如下
/**
* 获取时间段内所有月份集合
* @param beginMonth 开始年月
* @param endMonth 结束年月
**/
private List<String> getMonths(String beginMonth,String endMonth){
List<String> result = new ArrayList<>();
Date beginDate = DateUtils.getDate("yyyyMM",beginMonth);
Date endDate = DateUtils.getDate("yyyyMM",endMonth);
if (beginDate.after(endDate)) {
throw new BusiException("时间入参非法");
}
result.add(beginMonth);
Calendar cal = Calendar.getInstance();
Date originalDate = beginDate;
while (endDate.after(originalDate)) {
cal.setTime(originalDate);
cal.add(Calendar.MONTH, 1);
originalDate = cal.getTime();
result.add(DateUtils.getFormatDate(originalDate).substring(0,6));
}
return result;
}
确认线程个数
拿到所有月份后,进行分多线程处理的操作,增加单位时间内查询表的个数,以此缩短查询时间,通常我们都利用线程池来进行多线程操作。这里会涉及线程池大小的考虑问题,可以参考以下博文:计算线程池最佳线程数。我们姑且用CPU复杂型公式进行计算
int cpuNums = Runtime.getRuntime().availableProcessors() + 1;
均匀分配数据
确定好线程的大小,我们还要考虑一个问题,那就是我们如何为一个线程均匀地分配数据的处理量,在当前的需求下,就是如何均匀地为每个线程分配对应处理的月份,可以参考以下代码:
/**
* 平衡分组算法 - 已知分配份数
* @param sourceList 数据源
* @param groupNum 被非配份数
**/
public static <T> List<T>[] spiltDataList(List<T> sourceList,int groupNum){
List<T> [] group = new List[groupNum];
/* 初始化数组 */
for (int i = 0 ; i < groupNum ; i++) {
group[i] = new ArrayList<>();
}
int sourceSize = sourceList.size();
int batchNum = sourceSize % groupNum == 0 ? sourceSize / groupNum : sourceSize / groupNum + 1;
for (int i = 1; i <= batchNum ; i++){
if (i == batchNum){
int finalBatchNum = sourceSize - (i - 1) * groupNum;
for (int j = 0 ; j < finalBatchNum ; j++){
group[j].add(sourceList.get((i - 1) * groupNum + j));
}
}else {
for (int j = 0 ; j < groupNum ; j++){
group[j].add(sourceList.get((i - 1) * groupNum + j));
}
}
}
return group;
}
多线程实现
要对所有子线程进行汇总,就必须使用Callable和Future的方式来实现多线程,我们就可以拿到每个子线程的查询返回,进而汇总分析处理。关于多线程实现方式,可以参考Java多线程事务管理中对多线程实现方式的介绍
以下为核心代码实现
/**
* 银行扣费查询 - 多线程方案
* @param qryType 查询类型
* @param qryValue 查询值
* @param payType 扣费类型
* @param beginMonth 开始年月
* @param endMonth 结束年月
**/
public List<CollInfoQueryBo> collInfoQueryByThread(
String qryType,Long qryValue,String payType,String beginMonth,String endMonth)
throws InterruptedException,ExecutionException{
List<CollInfoQueryBo> collInfoQueryBos = new ArrayList<>();
List<String> months = getMonths(beginMonth,endMonth);
int cpuNums = Runtime.getRuntime().availableProcessors() + 1;
int totalNum = months.size();
int threadNum;
if (totalNum < cpuNums){
threadNum = totalNum;
}else {
threadNum = cpuNums;
}
/* 分线程处理 */
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(threadNum);
CountDownLatch endLock = new CountDownLatch(threadNum);
BlockingQueue<Future<List<CollInfoQueryBo>>> queue = new LinkedBlockingQueue<>();
List<String>[] stringList = spiltDataList(months,threadNum);
for (List<String> monthList : stringList) {
Future<List<CollInfoQueryBo>> future = fixedThreadPool.submit(new Callable<List<CollInfoQueryBo>>() {
@Override
public List<CollInfoQueryBo> call() throws Exception {
List<CollInfoQueryBo> collInfoQueryBoList = getAllMonthResult(monthList,qryType,qryValue);
endLock.countDown();
return collInfoQueryBoList;
}
});
queue.add(future);
}
endLock.await();
/* 汇总结果 */
for(Future<List<CollInfoQueryBo>> future : queue) {
List<CollInfoQueryBo> currentThreadList = future.get();
collInfoQueryBos.addAll(currentThreadList);
}
fixedThreadPool.shutdown(); //关闭线程池
return collInfoQueryBos;
}
Java数据库分表与多线程查询结果汇总的更多相关文章
- MySQL订单分库分表多维度查询
转自:http://blog.itpub.net/29254281/viewspace-2086198/ MySQL订单分库分表多维度查询 MySQL分库分表,一般只能按照一个维度进行查询. 以订单 ...
- MySQL数据库分表的3种方法
原文地址:MySQL数据库分表的3种方法作者:dreamboycx 一,先说一下为什么要分表 当一张的数据达到几百万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目 ...
- zabbix 数据库分表操作
近期zabbix数据库占用的io高,在页面查看图形很慢,而且数据表已经很大,将采用把数据库的数据目录移到新的磁盘,将几个大表进行分表操作 一.数据迁移: 1.数据同步到新的磁盘上,先停止mysql(不 ...
- mysql 数据库 分表后 怎么进行分页查询?Mysql分库分表方案?
Mysql分库分表方案 1.为什么要分表: 当一张表的数据达到几千万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目的就在于此,减小数据库的负担,缩短查询时间. m ...
- Oracle亿级数据查询处理(数据库分表、分区实战)
大数据量的查询,不仅查询速度非常慢,而且还会导致数据库经常宕机(刚接到这个项目时候,数据库经常宕机o(╯□╰)o). 那么,如何处理上亿级的数据量呢?如何从数据库经常宕机到上亿数据秒查?仅以此篇文章作 ...
- 数据库分表分区后的ID生成之雪花生成
转自https://www.cnblogs.com/jajian/p/11101213.html 传统的单体架构的时候,我们基本是单库然后业务单表的结构.每个业务表的ID一般我们都是从1增,通过AUT ...
- Hibernate与数据库分表
数据库分片(shard)是一种在数据库的某些表变得特别大的时候采用的一种技术. 通过按照一定的维度将表切分,可以使该表在常用的检索中保持较高的效率,而那些不常用的记录则保存在低访问表中.比如:销售记录 ...
- 一致性Hash算法在数据库分表中的实践
最近有一个项目,其中某个功能单表数据在可预估的未来达到了亿级,初步估算在90亿左右.与同事详细讨论后,决定采用一致性Hash算法来完成数据库的自动扩容和数据迁移.整个程序细节由我同事完成,我只是将其理 ...
- 数据库分表之Mybatis+Mysql实践(含部分关键代码)
2018年01月31日 随着我们系统用户数量的日增,业务数据处于一个爆发前,增长的数据量已经给我们的系统造成了很大的不确定.在上个周末用户量较多,并发较大的情况下,读写频繁的验证码表,数据量 ...
随机推荐
- Appium_Android自动化测试Genymotion之模拟器联网设置
目的: 使用Genymotion做Android项目,需要考虑到联网,以下是设置操作 操作步骤: 打开VM VirtualBox,设置->网络-> 启动模拟器,设置 备注: 模拟器的手机 ...
- 解决使用Git找不到.ssh文件夹的办法
解决使用Git找不到.ssh文件夹的办法 首先生成git密钥 git config --global user.name "你的名字(最好是中文全名)" git config -- ...
- 【NX二次开发】Block UI 线型
属性说明 常规 类型 描述 BlockID String 控件ID Enable Logical 是否可操作 Group Logical ...
- 【C++】Vector求最大值最小值
最大值: int max = *max_element(v.begin(),v.end()); 最小值: int min = *min_element(v.begin(),v.end());
- .NET平台系列31:.NET团队送给.NET开发人员的云原生学习资源汇总
系列目录 [已更新最新开发文章,点击查看详细] .NET Core 启动于2016年,跟K8S同年诞生,既拥有着悠久的历史积累,又集成了当下最新的设计理念,加上.NET团队持续对容器技术的官方 ...
- 【Azure 机器人】微软Azure Bot 编辑器系列(4) : 使用语言生成功能[LG: Language Generation] (The Bot Framework Composer tutorials)
欢迎来到微软机器人编辑器使用教程,从这里开始,创建一个简单的机器人. 在该系列文章中,每一篇都将通过添加更多的功能来构建机器人.当完成教程中的全部内容后,你将成功的创建一个天气机器人(Weather ...
- Linux安装界面简介
1.安装欢迎界面:install or upgrade an exsiting system:安装或升级现有系统 install system with basic video driver:安装过程 ...
- linux安装配置交叉编译器arm-linux-gnueabi-gcc
要使我们在x86架构下运行的程序迁移至ARM架构的开发板中运行时,需要通过交叉编译器将x86下编写的程序进行编译后,开发版才能运行. 在安装之前我们需要了解,什么是交叉编译器. 一.下载交叉编译器 这 ...
- [Django REST framework - 序列化组件、source、钩子函数]
[Django REST framework - 序列化组件.source.钩子函数] 序列化器-Serializer 什么是rest_framework序列化? 在写前后端不分离的项目时: 我们有f ...
- Redis的Pipeline、事务和lua
1. Pipeline 1.1 Pipeline概念 Redis客户端执行一条命令分别为如下4个过程: 1) 发送命令 2) 命令排队 3) 命令执行 4) 返回结果 其中1)+4)称为Round T ...