针对亿量级数据MongoDB查询注意点

  1. 获取集合总记录,不用countDocuments,改用 estimatedDocumentCount

  1. 分页用ObjectId排序定位(可结合其他查询条件),再用limit获取指定记录后的记录数。

  1. limit获取的页大小,需要在匹配记录范围内,否则会导致 游标 hasNext 操作挂住。

比如:在5亿记录的集合里(索引有 day、ObjectId),指定 day=20220308的记录数有2000001,每次取5000条记录,需分401次查询。最后一次传参页大小应用1,而不是5000作为limit参数。如果大于1,会导致mongo又从指定day条件遍历符合条件数据。

注:以上场景是指用ObjectId作为索引的情况下,如果索引字段全部是数字类型,无此问题。

查询接口:

    /**
     * 根据查询条件查询数据(原生对象查询方式)
     * 
     * @param collectionName 集合名称
     * @param filter 过滤条件对象
     * @param sorts 排序对象
     * @param cls 返回对象类型
     * @param page 查询页码(<=0表示不分页)
     * @param pagesize 页大小(<=0表示获取匹配条件所有记录)
     * @return
     */
    public <T>Page<T> queryDocument(String collectionName, Bson filter, List<SortField> sorts, Class<T> cls, int page, int pagesize){
        try {
            MongoCollection<Document> collection = this.getCollectionObject(collectionName);

            Page<T> ret = new Page<>();
            ret.setPageInfo(page, pagesize, 0, 0, null);

            long rows = 0;
            int pages = 1;
            FindIterable<Document> docs = null;
            //查询
            if (filter!=null) {
                if (page>0) rows = collection.countDocuments(filter);
                docs = collection.find(filter);
            }
            else {
                if (page>0) rows = collection.estimatedDocumentCount();
                docs = collection.find();
            }
            if (docs==null) return ret;

            //处理排序
            Bson sort = toQuerySort(sorts);
            if (sort!=null) docs = docs.sort(sort);
            //处理一页数据
            if (pagesize>0) {
                pages = calculatePage(rows, pagesize);

                if (page>0) docs = docs.skip((page-1) * pagesize).limit(pagesize);
                else docs = docs.limit(pagesize);

                if (page>pages) return ret;
            }

            //取出数据
            List<T> data = new ArrayList<>();
            MongoCursor<Document> cursor = docs.iterator();
            //注意: 在大数据情况下,如果页大小大于匹配的记录数,hasNext方法将挂起。正确做法应该传入匹配记录数作为页大小.
            while (cursor.hasNext()) {
                data.add(toBean(cursor.next(), cls));
            }
            cursor.close();
            ret.setPageInfo(page, pagesize, pages, (int)rows, data);
            return ret;
        }catch(Exception e) {
            Log4j2Tool.error(MongoCliDrv.class, e);
            throw new AppException(SysErr.DB_SQL_ERR, "查询文档记录失败!");
        }
    }
具体应用场景:

    /**
     * 一次处理一天源数据
     * 
     * @param log
     * @return
     */
    private static boolean splitAsinKeyword(LogAbaTask log) {
        try {
            // List<AbaKeywordsIndex> keysIdx = new ArrayList<>();
            Map<String, AsinKeywords> swaps = new HashMap<>();

            AbaMetricsDao dao = new AbaMetricsDao();
            int total = dao.getDayRecordCount(log.getDay());
            int count=0;
            int remain=total;

            List<AbaDataEx> list = null;
            String lastId = log.getGuid();
            while (remain>0) {
                Log4j2Tool.info(AbaMetricsToAsinUtils.class, String.format("正在分析数据:%d/%d...", count,total));
                list = dao.getAbaMetrics(log.getDay(), lastId, remain>BATCH_SIZE ? BATCH_SIZE : remain);
                count += list.size();
                remain = total-count;

                for(AbaData item: list) {
                    String keyId = EncryptTool.md5Encode(item.getQuery());
                    // 转关键词列表
                    //keysIdx.add(new AbaKeywordsIndex(keyId, item.getQuery()));
                    // 转ASIN关键词
                    for(ClickAsin clickAsin: item.getClickAsins()) {
                        AsinKeywords asinKeywords = swaps.get(clickAsin.getAsin());
                        if (asinKeywords==null) asinKeywords = new AsinKeywords(clickAsin.getAsin(), item.getDay());
                        asinKeywords.addKeyword(keyId, item.getSearchRank());

                        swaps.put(clickAsin.getAsin(), asinKeywords);
                    }
                }
                // 保存关键词索引表(TODO 可以单独放一个任务执行)
                //saveKeywordIndex(keysIdx);
                // 获取下一批数据记录Id
                lastId = list.get(list.size()-1).getGuid();
            }
            MongoCliDrv.closeConnection();

            // 保存ASIN关键词表
            saveAsinKeywords(log.getDay(), swaps);

            log.setGuid(null);
            Date currentDate = DateTimeTool.intToDate(log.getDay());
            int nextDay = DateTimeTool.dateToInt(DateTimeTool.incDay(currentDate, 1));
            log.setDay(nextDay);
            log.setCollectionName(AbaMetricsDao.getCollectionName(nextDay));
            Log4j2Tool.info(AbaMetricsToAsinUtils.class, log.getCollectionName()+" 已处理");
            return true;
        }catch(Exception e) {
            Log4j2Tool.error(AbaMetricsToAsinUtils.class, e);
            return false;
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值