今天接到一个任务,线上的mongodb积累了大量的无用数据,导致宕机,现在对里面的数据进行批量删除。

其中库里面的一个log记录有2000w+条,他的存储字段比较少,格式如下:

{ "_id" : ObjectId("5ecb648b17bee8673ef09024"), "level" : 1, "pay" : 0, "rand" : 64090, "uid" : NumberLong(120196967) }

我们对这个表里面的删除记录就是将1年以上的数据进行清楚,根据ObjectId这个字段的生成规则,用如下的方法判断:

Date date1 = new Date();
System.out.println("开始统计要删除的时间 :" + date1);
DBCursor dbCursor = collection.find();
int count = 0;
while(dbCursor.hasNext()) {DBObject object = dbCursor.next();String id = object.get("_id").toString();String time16Str = id.substring(0, 8);long createTime = str16To10(time16Str);if(createTime < lastTime) {count++;}
}
Date date2 = new Date();
System.out.println("结束统计要删除的时间 :" + date2);
System.out.println("统计花费的时间 :" + (date2.getTime()-date1.getTime()) + "ms");
System.out.println("dbCursor count :" + dbCursor.count());
System.out.println("need del  count :" + count);

统计运行的时间如下,从下图中可以看出2000+的统计时间大约1分钟不到,这个还是还是可以接收的:

开始统计要删除的时间 :Tue May 26 18:38:07 CST 2020
结束统计要删除的时间 :Tue May 26 18:38:50 CST 2020
统计花费的时间 :43079ms
dbCursor count :21595385
need del  count :16398864

然后在if中添加删除的两行代码:

if(createTime < lastTime) {            DBObject query = new BasicDBObject().append("_id", new ObjectId(id));WriteResult result = collection.remove(query);      //1// collection.findAndModify(query, null, null, true, null, false, false); //2
}

上面两行代码的执行删除的时间截然不同,remove5分59s删除了36956,而findAndModify5分3s删除了127090,如果是要删除16398864条数据的话,remove大约需要44h,而findAndModify需要10h。

针对这种批量删除,设计的方案如下:

public Long clearUserRecommandOldData() {// 根据objectId清理两年之前的老数据Calendar calendar = Calendar.getInstance();calendar.add(Calendar.YEAR, -2);final long lastTime = calendar.getTime().getTime();final OpLogService service = this.opLogService;Long result = new MongoExecutor<Long>() {@Overrideprotected Long doInMongo(MongoClient client) {ExecutorService executorService = Executors.newFixedThreadPool(2);DB database = client.getDB(ConfigManager.get(GeoMongodbConfig.class).getServer().getDbName());DBCollection collection = database.getCollection(COLLECTION_NAME);DBObject fields = new BasicDBObject();fields.put("_id" , true);// 游标每次从库里面一次拿出5w条数据,避免出现SocketTimeOut异常DBCursor dbCursor = collection.find(new BasicDBObject(), fields).batchSize(CloverConstants.DBCUSTOR_BATCH_SIZE);long count = 0;List<Object> objectIds = new ArrayList<>();Date date1 = new Date();logger.info("user_recommand开始统计, date:" + date1 + ", dbCursor count:" + dbCursor.count());while(dbCursor.hasNext()) {DBObject object = dbCursor.next();String id = object.get("_id").toString();String time16Str = id.substring(0, 8);  // 获取objectId中的16进制的创建时间long createTime = Long.valueOf(time16Str, 16) * 1000;if (createTime < lastTime) {count++;objectIds.add(object.get("_id"));// 每次取出100条数据就另外开启一个线程来执行删除任务if(count % CloverConstants.DBCUSTOR_BATCH_SIZE == 0) {final List<Object> delObjectIds = new ArrayList<>(objectIds);objectIds.clear();threadPoolHandler(executorService, delObjectIds);}}}dbCursor.close();Date date2 = new Date();logger.info("user_recommand结束统计, date:" + date2 + ", cost time:" + (date2.getTime()-date1.getTime()) + "ms");threadPoolHandler(executorService, objectIds);return count;}}.execute();if(result == null) {result = 0l;}return result;
}private void threadPoolHandler(final ExecutorService executorService, final List<Object> objects) {new MongoExecutor<Long>() {@Overrideprotected Long doInMongo(MongoClient client) {DB database = client.getDB(ConfigManager.get(GeoMongodbConfig.class).getServer().getDbName());final DBCollection collection = database.getCollection(COLLECTION_NAME);executorService.execute(new Runnable() {@Overridepublic void run() {Date date1 = new Date();logger.info("user_recommand开始删除, date:" + date1 + ", count:" + objects.size());for(Object objectId : objects) {DBObject query = new BasicDBObject().append("_id", objectId);// 这个删除方法效率是remove的至少10倍collection.findAndModify(query, null, null, true, null, false, false);}Date date2 = new Date();logger.info("user_recommand结束删除, date:" + date2 + ", cost time:" + (date2.getTime()-date1.getTime()) + "ms");}});return 0l;}}.execute();
}

这个方案的重要一点这这一行代码:

DBCursor dbCursor = collection.find(newBasicDBObject(), fields).batchSize(CloverConstants.DBCUSTOR_BATCH_SIZE);

find()中的两个参数,第一个表示查询条件,我这里是什么查询条件也没有,第二个表示查询返回的字段,这里一定要写,如果你只需要几个查询字段,一定要带上这个,上面的一条log记录它的json结构很小,如果你的json结构很大的话,每次查询返回一个很大的DBObject,会造成系统频繁的gc。

batchSize()中是这个数字我写的是100,表示游标每次从数据库一次拿100条数据到内存,这个值,不能设置的太大,也不要设置太小。

batchSize设置太大,会出现SocketTimeOut超时问题,设置太小,游标每次从数据库拿一条效率也很低。

现在的mongodb启动时设置的SocketTimeOut时间时5s,batchSize时100,这个也是测试设置了好几种才决定的。

代码里面还有一个很重要的点,就是下面这行:

// 每次取出100条数据就另外开启一个线程来执行删除任务
if(count % CloverConstants.DBCUSTOR_BATCH_SIZE == 0) {
final List<Object> delObjectIds = new ArrayList<>(objectIds);
objectIds.clear();
threadPoolHandler(executorService, delObjectIds);
}

每收集100条可删除的数据,然后就放到线程池里面去执行,线程池初始化设置了2个,

当前条件改成删除两年前的老数据,库里面一共时2100+的数据,删除数据有500+万条数据,耗时15min左右就删干净了,看了一下数据库里面增加的不是很频繁,在系统里面添加了一个定时器,每两个星期清理一次Mongodb中的数据。

补充:

上面这个方案弊端是一边遍历一边删除,dbCustor的游标一直在不停的变化,很可能在删除的时候会出现超时的异常,具体如下图:

上面这个就是某些数据删除过慢,在日志中打印出来的,之前用remove()做删除这个时间消耗的有2s-20s左右的都有。但是如果删除不是那么频繁的话就不会出现这个问题。

我们项目中需求是定时清理过期的数据,这张表现在有2100w+的记录,删除当前时间两年前的数据大约有500w+条,线上一个星期大约增长了10w+条数据,之前在本次测试23分钟大约删除了18w条,现在我做的删除具体代码如下:

public Long clearUserRecommandOldData(final boolean auto) {final TwoTuple<Long, Long> executeTime = opLogService.getExecuteTime(COLLECTION_NAME, auto);// 判断当前的任务是否在可执行的指定时间片中if (executeTime.getFirst() == 0l || executeTime.getSecond() == 0l) {return 0l;}long now = System.currentTimeMillis();if (now < executeTime.getFirst() || now > executeTime.getSecond()) {return 0l;}// 根据objectId清理两年之前的老数据Calendar calendar = Calendar.getInstance();calendar.add(Calendar.YEAR, -2);final long twoYearsAgo = calendar.getTime().getTime();final OpLogService service = this.opLogService;// 返回字段DBObject fields = new BasicDBObject();fields.put("_id", true);// 排序条件DBObject orderBy = new BasicDBObject();orderBy.put("_id", 1);  // 时间升序long count = 0;try {MongoClient client = MongoManager.getInstance().getClient();DB database = client.getDB(ConfigManager.get(GeoMongodbConfig.class).getServer().getDbName());DBCollection collection = database.getCollection(COLLECTION_NAME);// 游标每次从库里面一次拿出100条数据,避免出现SocketTimeOut异常,// 按_id升序排列,时间大于lastTime就立马返回,不用遍历所有DBCursor dbCursor = collection.find(new BasicDBObject(), fields).sort(orderBy).batchSize(CloverConstants.DBCUSTOR_BATCH_SIZE);Date date1 = new Date();logger.error("user_recommand开始统计, date:" + date1 + ", dbCursor count:" + dbCursor.count());List<Object> objectIds = new ArrayList<>();while (dbCursor.hasNext()) {DBObject object = dbCursor.next();String id = object.get("_id").toString();String time16Str = id.substring(0, 8);  // 获取objectId中的16进制的创建时间long createTime = Long.valueOf(time16Str, 16) * 1000;if (createTime > twoYearsAgo)break;count++;objectIds.add(object.get("_id"));// 每次取出100条数据就来执行删除任务if (count % CloverConstants.DBCUSTOR_BATCH_SIZE == 0) {final List<Object> delObjectIds = new ArrayList<>(objectIds);objectIds.clear();// 避免删除频繁,让出一些空闲时间给主程序调用mongodbtry {Thread.sleep(500);} catch (Exception e) {logger.error(COLLECTION_NAME + " clear sleep error" + e);}// 继续删除service.delOldData(collection, delObjectIds, COLLECTION_NAME, 0);}// 如果划分的时间片用完,就结束执行,保证别的日志也能被清理到if (System.currentTimeMillis() >= executeTime.getSecond()) {logger.error(COLLECTION_NAME + " time period over");break;}}dbCursor.close();Date date2 = new Date();logger.error("user_recommand结束统计, date:" + date2 + ", cost time:" + (date2.getTime() - date1.getTime()) + "ms, del count:" + count);service.delOldData(collection, objectIds, COLLECTION_NAME, 0);return count;} catch (Exception e) {logger.error(COLLECTION_NAME + "execute error:" + e.getMessage());count += clearUserRecommandOldData(auto);}return count;
}public void delOldData(DBCollection collection, List<Object> objectIds, String colName, int startIndex) {if (objectIds.isEmpty())return;if (startIndex >= objectIds.size())return;//Date date1 = new Date();//logger.info(colName + "开始删除, date:" + date1 + ", count:" + objectIds.size());try {for (int i = startIndex; i < objectIds.size(); i++) {DBObject query = new BasicDBObject().append("_id", objectIds.get(startIndex));// 比remove删除效率高10+倍collection.findAndModify(query, null, null, true, null, false, false);startIndex++;//collection.remove(query);}} catch (MongoException exception) {logger.error("MongoException error, _id:" + objectIds.get(startIndex) + "error msg:" + exception.getMessage());startIndex++;delOldData(collection, objectIds, colName, startIndex);}//Date date2 = new Date();//logger.info(colName + "结束删除, date:" + date2 + ", cost time:" + (date2.getTime() - date1.getTime()) + "ms");}

具体删除策略如下:

线上的connections连接数配置的是5,所以采用的单线程删除策略;

开启一个定时器,每天3点-7点执行,每张表规定一个时间段删除,保证有限的时间内每张表都能够有时间删到;

保证你的时间段删除的数量比它每天的增长量要多,不然你怎么都删不完就尴尬了;

如果你的删除数据任务不是要求立马删完,可以删除一些数据之后,让线程休眠一段时间再继续删除,不然频繁删除不仅会一直占用着连接,线上写数据可能会有影响,而且会报SocketTimeOut异常;

public TwoTuple<Long, Long> getExecuteTime(String colName, boolean auto) {long startTime = 0l;long endTime = 0l;Calendar c = Calendar.getInstance();int nowHour = c.get(Calendar.HOUR_OF_DAY);long now = c.getTimeInMillis();if (auto) {// 定时器触发 3-7点之间,3-5执行user_recommend, 5-6执行op_log, 6-7执行op_resultint endHour = CloverConstants.CLEAR_END_HOUR;if (colName.equals(FriendRecService.COLLECTION_NAME)) {endHour = 5;} else if (colName.equals(COL_OP_LOG)) {endHour = 6;}startTime = now;c.set(c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH), endHour, 0, 0);endTime = c.getTimeInMillis();} else {// 手动触发,如果碰上定时器的执行时间段则不执行startTime = now;long endTime1 = 0l;if (nowHour < CloverConstants.CLEAR_START_HOUR) {c.set(c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH), CloverConstants.CLEAR_START_HOUR, 0, 0);endTime1 = c.getTimeInMillis() - 5 * HeConsts.MILLISECONDS_PER_MINUTE;if (startTime > endTime1) {return Tuple.tuple(0l, 0l);}} else if (nowHour >= CloverConstants.CLEAR_END_HOUR) {c.add(Calendar.DAY_OF_MONTH, 1);c.set(c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH), CloverConstants.CLEAR_START_HOUR, 0, 0);endTime1 = c.getTimeInMillis() - 5 * HeConsts.MILLISECONDS_PER_MINUTE;} else {return Tuple.tuple(0l, 0l);}// 手动触发,每个表删除时间分配2小时,保证手动执行每个表也有时间去删到endTime = startTime + HeConsts.MILLISECONDS_PER_HOUR * 2;//endTime = startTime + 5*60*1000;  // for testif (endTime > endTime1) {endTime = endTime1;}}return Tuple.tuple(startTime, endTime);}

mongodb批量删除数据效率问题相关推荐

  1. java 批量删除数据_一种批量删除数据的方法

    这两天碰见一个比较紧急的生产问题,由于还在处理中,所以暂时不能给出整体描述,但其中涉及的一个问题就是删除一张大表中的过期历史数据,针对不同的类型的表可能有不同的解决方法,比如若是按照时间做的分区表,d ...

  2. think php批量删除代码,ThinkPHP实现批量删除数据的代码实例

    ThinkPHP实现批量删除数据的代码实例 ThinkPHP实现批量删除数据原理很简单,只需在模板页面里面写上这样传过来就是一个数组,action的删除函数del()如下: /** **删除函数支持删 ...

  3. 【RecyclerView】 十一、RecyclerView 数据更新 ( 删除单条数据 | 批量删除数据 )

    文章目录 一.删除单条数据 二.批量删除数据 三.完整代码示例 四.RecyclerView 相关资料 一.删除单条数据 删除单条数据 : 调用 RecyclerView.Adapter 的 void ...

  4. ASP.NET MVC+EF框架+EasyUI实现权限管理系列(18)-过滤器的使用和批量删除数据(伪删除和直接删除)...

    ASP.NET MVC+EF框架+EasyUI实现权限管理系列(18)-过滤器的使用和批量删除数据(伪删除和直接删除) 原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(18)-过 ...

  5. mysql批量删除数据库_数据库实现批量删除数据的操作方法(代码实例)

    本篇文章就给大家介绍数据库实现批量删除数据的操作方法.有一定的参考价值,有需要的朋友可以参考一下,希望对你们有所帮助.package com.mescs.baseinfo.utils; import ...

  6. php批量删除例子,php批量删除数据完整实例代码

    php批量删除数据完整实例代码2017-11-11 21:03 网站后台,一般都有相关的数据,以列表的形式来展现,也方便管理. 有些数据,我们可能需要对其批量进行操作,比如删除.更新日期等. 今天小编 ...

  7. Mybatis 如何批量删除数据

    Mybatis如何批量删除数据 本期以最常用的根据id批量删除数据为例: 接口设计1:List类型单参数 Integer deleteByIds(List<Integer> ids); 接 ...

  8. SQL批量删除数据操作

    SQL批量删除数据操作 文章目录 SQL批量删除数据操作 sql语句 DELETE和TRUNCATE区别 sql语句 --删除数据(避免这么写,删除表全部数据) DELETE FROM `studen ...

  9. Oracle中批量删除数据

    在一个ORACLE数据库运行过程中,有时候会遇到要批量删除数据的情况,如一个保存历史数据的表中有大量的数据已经不需要保留,要将这部分数据删除.以下就这种情况可以采取的方法进行讨论.   1.使用TRU ...

最新文章

  1. cpu内存调度python_Python/Pycharm内存和CPU分配以提高运行速度?
  2. 四十七、Tableau地理可视化
  3. microsoft计算机调制管理器,Microsoft
  4. 刚刚,阿里云上线六大“战疫情”项目
  5. RMAN-06023: no backup or copy of datafile 6 found to restore
  6. 如何按距离排序 php,php做附近的人,根据距离由近到远进行排序
  7. 史上最全最新手机号码号段大全
  8. python复数类型及其特点和分布地区_各种气候类型及其特点、分布规律、主要分布地区、数值特征...
  9. 从零开始手写 VIO
  10. 【CSAPP】计算机系统知识点(新国立NUS课程,中文版)
  11. 19 款国产手机无一幸免:15 分钟破解人脸识别,打印眼镜让刷脸形同虚设
  12. 2022第二届中国新能源汽车功能安全创新国际峰会
  13. win10推送_win10无线镜像投屏电视
  14. 7 士兵排队---PTA(排序+中位数)(C++)
  15. python怎么多行输入_python如何输入多行
  16. 集成隔离电源设计 ,满足EMI目标
  17. 使用Oh-My-Posh美化FluentTerminal
  18. 2022.05 ESP32 空中升级 OTA
  19. php 程序发送谷歌邮件
  20. 【信管3.1】项目整体管理概念与过程

热门文章

  1. 《当代教育实践与教学研究》期刊简介及投稿要求
  2. 机车风暴3制作揭密译文
  3. apple watch se os8 初体验 辅助触控
  4. Nginx - 深入理解nginx的处理请求、进程关系和配置文件重载
  5. 新型智能健身房如何破局重新定义健身行业
  6. Nimbus二storm启动nimbus源码分析-nimbus.clj
  7. 苗木产业已成为促进冠县农民发家致富的绿色银行
  8. PhotoShop - 图片圆角化+指定选区背景替换
  9. macOS_Ventura_13.0_22A380可引导可虚拟机安装的纯净版苹果OS系统ISO镜像安装包免费下载
  10. 物联网和区块链技术在物流行业的应用