业务背景

系统中有个mongodb集合每天增长上千万,时间长了,系统中这个集合已经有几亿数据了,需要写一个定时任务,把集合中三个月前的数据删除,并且后面每天凌晨执行这个定时任务.

方案分析

方案一

使用Mongo的游标MongoCursor,遍历获取集合id获取id列表idList,然后根据idList批量删除

方案二

使用mongoTemplate.find,每次查询1万条数据,遍历获取集合id获取id列表idList,然后根据idList批量删除

代码片段

方案一

    /**
     * 删除集合数据
     *
     * @param time 传三个月前的时间戳
     */
    private void deleteData(Long time) {

        Query query = new Query(new Criteria("ct").lt(time));

        MongoCursor<Document> dbCursor = mongoTemplate.getCollection(COLLECTION_NAME)
                .find(query.getQueryObject())
                //设置游标查询不超时
                .noCursorTimeout(true)
                //设置批量从数据库中获取的数据量
                .batchSize(10000)
                .iterator();

        int i = 0;
        List<String> idList = new ArrayList<>();
        Document next;
        while (dbCursor.hasNext()) {
            i++;
            next = dbCursor.next();
            String id = next.get("_id").toString();
            idList.add(id);

            if (i % 10000 == 0) {

                List<String> idListToDelete = new ArrayList<>();
                idListToDelete.addAll(idList);

                // 清空数据
                idList.clear();

                log.info("delete: {}", i);

                // 异步删除数据
                threadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        doDeleteData(idListToDelete);
                    }
                });
            }
        }

        // 再次判断
        if (CollectionUtils.isNotEmpty(idList)) {
            doDeleteData(idList);
            idList.clear();
        }

        log.info("data delete total: {}", i);
    }

方案二

    /**
     * 删除集合数据
     *
     * @param time 传3个月前时间戳
     */
    private void deleteData(Long time) {

        Query query = new Query(new Criteria("ct").lt(time));
        // 只需要去_id字段即可,避免取多个字段,数据量大了占内存过多
        query.fields().include("_id");
        query.limit(10000);

        List<XXX> xxxList = mongoTemplate.find(query, XXX.class);
        List<String> idListToDelete;
        int i = 0;
        while (CollectionUtils.isNotEmpty(xxxList)) {
            i += xxxList.size();
            idListToDelete = xxxList.stream().map(o -> o.getId()).collect(Collectors.toList());
            doDeleteData(idListToDelete);
            log.info("delete: {}", i);

            xxxList = mongoTemplate.find(query, XXX.class);
        }

        log.info("delete total: {}", i);
    }
    /**
     * 执行删除数据
     */
    private void doDeleteData(List<String> idList) {
        long s0 = System.currentTimeMillis();
        Query query = new Query(new Criteria("_id").in(idList));
        mongoTemplate.remove(query, XXX.class);
        long s1 = System.currentTimeMillis();
        log.info("delete data size: {}, cost: {}", idList.size(), (s1 - s0));
    }

对比

两种方案都是可行的,只不过是速度上的差别,大致速度如下(当时没有记录数据,大致是这个速度)

发送量方案一(游标)方案二(query)
10万7s4s
400万7分2分

总结

实际在测试的过程中,发现删除数据其实花费时间很少,大概200多毫秒删1万条,但MongoCursor在遍历循环的时候比较耗时while (dbCursor.hasNext()) { }。

而mongoTemplate.find在查询的数据的时候,如果数据有索引,每次查询1万条其实速度是挺快的,根据找到符合条件的1万条数据,立即返回。(顺便说下,mongoTemplate.count计算总数比较慢)

遇到类似的问题,可以结合业务,对两种方案进行测试,一般第二种方案效率更高一点。

有其他更好的方案欢迎交流!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐