返回目录:经济新闻
说一下问题背景:当时做了一个项目,需要把MongoDB库里的数据(几个表)全部导出到文件,导出过程中有一定程度的简单过滤。当时库里的文件记录总量在几个亿,最终导出的文件大小累计约100G。
拿到需求,思考了一下定了一下方案:
1、服务暴露restfull接口供外部手动调用执行定时任务;
2、采用分布式定时任务框架(关于分布式定时任务框架后面会单独写一篇文章),进行多实例情况下定时导出任务调度;
3、导出任务执行时对于多个表并行进行处理;
4、导出时对于单个表,采用多线程并行导出;这里就产生了一个问题,一个表怎么多线程并行导出?
答:(1)先算出总表的数量T1,然后获取机器的总核数C1,拿出50%(预留一些核数给其他任务用)的核数当做并行线程总数;
(2)然后每个线程处理一部分数据,例如第一个线程处理0至2000万行,第二个线程处理2000万至4000万行;每个线程按分页循环查询自己范围内的数据,其中分页数支持配置。
按方案执行后,处理一些细小繁琐的业务控制问题后,按上面思路搭起来框架,本地构造了少量数据,测试很顺利。功能OK后,开始想测试一下性能,于是把差不多2000万的数据导入到本地数据库。导入后开始测试,开始几分钟都很快很符合预期,但是到了后面,导出基本开始卡住,很长时间才导出几千条,查看后台MongoDB日志,发现一次分页查询竟然需要10到20分钟,这让我陷入深思,于是开始翻看源码。
项目采用的是springboot,使用JPA进行数据库查询,一路翻下去,发现最终调用的是springboot里的MongoDB的查询方法。
<T> List<T> find(Query query, Class<T> entityClass);
这个方法很简单,第一个参数是查询的条件,第二个参数是返回的类型。那我实际传入的query是什么呢,就是一个sort条件,按id升序排列,然后用skip加limit分页查询。
BasicQuery queryCopy = new BasicQuery(queryObject);
if (null == queryCopy.getSortObject()||CollectionUtils.isEmpty(queryCopy.getSortObject().keySet())) {
queryCopy.with(new Sort(new Order(Direction.ASC, "_id")));
}
queryCopy.skip((pageNum - 1) * pageSize).limit(pageSize);
看到这里sort条件基本不会有问题,那问题就一定出在了skip加limit上,于是为了验证我的推论,我再去MongoDB后台观察,并调出了资源管理器。再次运行发现,当分页查询很慢时,电脑的内存使用率竟然到了90%多,但是磁盘读写却很低,CPU也没有被利用起来,所以排除了磁盘效率问题,确认是内存使用问题。
那为什么一个简单的查询会导致内存使用率会这么高呢?
原来MongoDB在处理skip limit时,由于指定了skip的数量,所以数据库会进行全表顺序扫描,扫描完你skip的数量后,再按你的limit获取指定的数据集。注意这里有一个全表顺序扫描的过程,如果你的数据量很少,那全表扫描很快,但是如果你的数据量很大,越到后面,每一次的分页查询都需要从第一行数据扫描到你当前要skip的行数。
举个例子,例如你的表数据总量是1000万,你的分页每页数量是1000,那如果你想查出第101个分页的数据时,MongoDB会需要先扫描完前100个分页的数据,也就是100*1000=100万,然后再扫描完的记录起,取你分页的配置(limit)1000条数据。
这时由于全表扫描,会占用大量的内存,同时由于没有索引可用,速度是非常慢的。
找到问题原因,解决方案也就有了:
造成查询速度慢的主要原因是全表扫描没用到索引;那如何让我们每次查询都用到索引呢?
1、由于开始的第一个分页不需要skip数据,所以非常快,当拿到第一个查询分页的结果后,得到他最后一条记录的id;
2、拿到上面的id后,第二次分页查询时,query里添加条件:记录的id大于上一个分页的最后一条记录的id;由于id上面有索引,所以这样能够非常快的定位到需要查询的记录的开始位置,然后再通过limit取出分页数据。
DBObject queryObject = new BasicDBObject();
if (null != query && null != query.getQueryObject()) {
queryObject = query.getQueryObject();
}
LinkedHashMap<Object, ObjectId> idCondition = new LinkedHashMap<>();
idCondition.put("$gt", new ObjectId(lastId));
queryObject.put("_id", idCondition);
BasicQuery queryCopy = new BasicQuery(queryObject);
queryCopy.with(new Sort(new Order(Direction.ASC, "_id")));
queryCopy.limit(pageSize);
经过上面的修复调整后,再进行试验,发现问题得到了解决,导出速度一直很稳定,CPU疯转,磁盘使用率也得到了充分利用,整个本本呜呜带风。最终几亿数据,半小时内就可以全部导出。
写在最后,由于这个问题已经有段时间了,没有记录下当时的日志,有些遗憾,不能更好的表述问题,但是基本思路就是上面这些,希望对大家有所帮助,如果您觉得有意义,关注我,后面会不定期还有分享。
相关阅读
mongodb分页查询优化_
- 百色金融新闻网经济新闻
- 说一下问题背景:当时做了一个项目,需要把MongoDB库里的数据(几个表)全部导出到文件,导出过程中有一定程度的简单过滤。当时库里的文件记录总量在几个亿,最终导出的文件大小
数据库的四种隔离级别中可读取未提交是指?_mysql数据库隔离级别
- 百色金融新闻网经济新闻
- 数据库事务的隔离级别有4种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable 。而且,在事务的并发操作中可能会出现脏读,不可重复读,幻读。下面通过事例一
最牛的基金是哪一只_基金排行天天基金网
- 百色金融新闻网经济新闻
- 财富管理WeFind: 在这个噪音时代,我们为您提供有质感、有营养的财富管理资讯。不求多,只求精。 圈主的话 话说对于买基金这件事,总是几家欢喜几家愁,买对基金报对大腿的小伙
互联网金融大数据分析_金融大数据风控平台
- 百色金融新闻网经济新闻
- 真正的金融科技是以数据为基础,以技术为手段。目前互联网金融界的领先企业如蚂蚁金服、京东金融可以达到95%以上的交易都用技术驱动数据来进行自动化风控、量化运营等业务。
广发银行信用卡申请未通过_因你的广发信用卡近期出现非正常
- 百色金融新闻网经济新闻
- 广发的信用卡在2018年的信用卡风控降额封卡的大潮中拔得头筹,10万额度的信用卡转眼间变成8元实在可怕,老实忠厚的广发在2018年一度让持卡人心惊胆战,多头授信、个人负债较高、
代还信用卡养卡违法吗_信用卡可以以卡养卡吗
- 百色金融新闻网经济新闻
- 之前讲过信用卡提额攻略,然而最近又有朋友咨询说是自己的信用卡都按正常操作消费,还款按时无逾期,且征信无不良记录,但是卡片的额度就是提不起来,仔细了解,才发现朋友一