百色金融新闻网
您的位置:百色金融新闻网 > 经济新闻 > mongodb分页查询优化_

mongodb分页查询优化_

作者:百色金融新闻网日期:

返回目录:经济新闻

说一下问题背景:当时做了一个项目,需要把MongoDB库里的数据(几个表)全部导出到文件,导出过程中有一定程度的简单过滤。当时库里的文件记录总量在几个亿,最终导出的文件大小累计约100G。

记一次亿级数据量MongoDB分页查询慢问题解决方案

拿到需求,思考了一下定了一下方案:

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);
记一次亿级数据量MongoDB分页查询慢问题解决方案

这个方法很简单,第一个参数是查询的条件,第二个参数是返回的类型。那我实际传入的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);
记一次亿级数据量MongoDB分页查询慢问题解决方案

看到这里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);
记一次亿级数据量MongoDB分页查询慢问题解决方案

经过上面的修复调整后,再进行试验,发现问题得到了解决,导出速度一直很稳定,CPU疯转,磁盘使用率也得到了充分利用,整个本本呜呜带风。最终几亿数据,半小时内就可以全部导出。

视频加载中...

写在最后,由于这个问题已经有段时间了,没有记录下当时的日志,有些遗憾,不能更好的表述问题,但是基本思路就是上面这些,希望对大家有所帮助,如果您觉得有意义,关注我,后面会不定期还有分享。

相关阅读

  • mongodb分页查询优化_

  • 百色金融新闻网经济新闻
  • 说一下问题背景:当时做了一个项目,需要把MongoDB库里的数据(几个表)全部导出到文件,导出过程中有一定程度的简单过滤。当时库里的文件记录总量在几个亿,最终导出的文件大小
  • 最牛的基金是哪一只_基金排行天天基金网

  • 百色金融新闻网经济新闻
  • 财富管理WeFind: 在这个噪音时代,我们为您提供有质感、有营养的财富管理资讯。不求多,只求精。 圈主的话 话说对于买基金这件事,总是几家欢喜几家愁,买对基金报对大腿的小伙
关键词不能为空

经济新闻_金融新闻_财经要闻_理财投资_理财保险_百色金融新闻网