文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

MongoDB导出场景查询优化 #1

2024-04-02 19:55

关注

原始链接:https://github.com/aCoder2013/blog/issues/1 转载请注明出处

引言

前段时间遇到一个类似导出数据场景,观察下来发现速度会越来越慢,导出100万数据需要耗费40-60分钟,从日志观察发现,耗时也是越来越高。

原因

从代码逻辑上看,这里采取了分批次导出的方式,类似前端的分页,具体是通过skip+limit的方式实现的,那么采用这种方式会有什么问题呢?我们google一下这两个接口的文档:

The cursor.skip() method is often expensive because it requires the server to walk from the 
beginning of the collection or index to get the offset or skip position before beginning to return 
results. As the offset (e.g. pageNumber above) increases, cursor.skip() will become slower and 
more CPU intensive. With larger collections, cursor.skip() may become IO bound.

简单来说,随着页数的增长,skip()会变得越来越慢,但是具体就我们这里导出的场景来说,按理说应该没必要每次都去重复计算,做一些无用功,我的理解应该可以拿到一个指针,慢慢遍历,简单google之后,我们发现果然是可以这样做的。

我们可以在持久层新增一个方法,返回一个cursor专门供上层去遍历数据,这样就不用再去遍历已经导出过的结果集,从O(N2)优化到了O(N),这里还可以指定一个batchSize,设置一次从MongoDB中抓取的数据量(元素个数),注意这里最大是4M.

比如说我这里配置的8000,那么mongo客户端就会去默认抓取这么多的数据量:

MongoDB导出场景查询优化 #1

经过本地简单的测试,我们发现性能已经有了飞跃的提升,导出30万数据,采用之前的方式,翻页到后面平均要500ms,总耗时60039ms。而优化后的方式,平均耗时在100ms-200ms之间,总耗时16667ms(中间包括业务逻辑的耗时)。

使用

DBCursor cursor = collection.find(query).batchSize(8000);
while (dbCursor.hasNext()) {
  DBObject nextItem = dbCursor.next();
  //业务代码
  ... 
  //
}

那么我们再看看hasNext内部的逻辑好吗?好的.

    @Override
    public boolean hasNext() {
        if (closed) {
            throw new IllegalStateException("Cursor has been closed");
        }

        if (nextBatch != null) {
            return true;
        }

        if (limitReached()) {
            return false;
        }

        while (serverCursor != null) {
            //这里会向mongo发送一条指令去抓取数据
            getMore();
            if (nextBatch != null) {
                return true;
            }
        }

        return false;
    }

    private void getMore() {
        Connection connection = connectionSource.getConnection();
        try {
            if(serverIsAtLeastVersionThreeDotTwo(connection.getDescription()){
                try {
//可以看到这里其实是调用了`nextBatch`指令        
initFromCommandResult(connection.command(namespace.getDatabaseName(),
                                                             asGetMoreCommandDocument(),
                                                             false,
                                                             new NoOpFieldNameValidator(),
                                                             CommandResultDocumentCodec.create(decoder, "nextBatch")));
                } catch (MongoCommandException e) {
                    throw translateCommandException(e, serverCursor);
                }
            } else {
                initFromQueryResult(connection.getMore(namespace, serverCursor.getId(),
                                                       getNumberToReturn(limit, batchSize, count),
                                                       decoder));
            }
            if (limitReached()) {
                killCursor(connection);
            }
        } finally {
            connection.release();
        }
    }

最后initFromCommandResult 拿到结果并解析成Bson对象

总结

我们平常写代码的时候,最好都能够针对每个方法、接口甚至是更细的粒度加上埋点,也可以设置成debug级别,这样利用log4j/logback等日志框架动态更新级别,可以随时查看耗时,从而更能够针对性的优化,对于本文说的这个场景,我们首先看看是不是代码的逻辑有问题,然后看看是不是数据库的问题,比如说没建索引、数据量过大等,再去想办法针对性的优化,而不要上来就撸代码。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-数据库
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯