最近遇到一个了一个MongoDB数据删除的问题,需要一次性删除上线即1.5年前~1年前的数据且之后每天清空一年过期的数据。在数据量比较大的情况下何种方式的删除效率最高是一个值得研究的问题,本文通过实际测试找出其中规律。
本文采用腾讯云mongodb集群进行验证。mongd配置较弱为2核4G,验证仅仅针对副本集不涉及分片集群。测试数据集如下,共计1000w条数据。接下来会以systime字段作为筛选条件验证影响删除操作耗时的因素都有哪些。
kfuin_beg = 0
kfuin_end = 1
for(var num = kfuin_beg; num < kfuin_end; num++){
var kfuin_num = num+1;//主号数
var cid_num = 10000 //1万个cid
var session_num = 10//每个cid有10通会话
var detail_num = 99//每个session有9条消息
var docs = [];
for(var i = num; i < kfuin_num; i++){
for(var j = 0; j < cid_num; j++){
for(var k = 0; k < session_num; k++){
var kfuin_val = i + 2852000000
var session_index_name_val = "event_session_" + kfuin_val.toString() + "_wx58b4690f0ab8193f-" + j.toString()
var session_id_val = "session_id" + "_wx58b4690f0ab8193f-" + j.toString() + "_" +(k).toString()
var systime_val = 1600000000000000 + j*10000 + k*1000
docs.push({kfuin:kfuin_val,key:session_index_name_val,session_id:session_id_val,systime:systime_val})
for(var h = 0; h < detail_num; h++){
var detail_index_val = "event_detail_" + kfuin_val.toString() + "_wx58b4690f0ab8193f-" + j.toString()
systime_val = 1600000000000000 + j*10000 + k*1000 + h
docs.push({kfuin:kfuin_val,key:detail_index_val,session_id:session_id_val,systime:systime_val})
}
}
print("complete cid!!")
}
}
print("insert docs!!")
db.coll.save(docs)
}
零、MongoDB删除数据不释放空间??
MongoDB 3.6版本之后默认使用的存储引擎是WiredTiger。
①这个引擎有个特点,那就是remove删除数据不释放空间。举个例子:你现在的集合里面有1亿条数据,占用10G空间;即便你删除了其中的99999999条数据都删了,其占用的空间仍然是10GB。
②如果想彻底释放空间,最直接的方法就是删除整个集合(db.coll_0.drop())或者删除整个数据库(dropDatabases())。
③虽然磁盘利用率下不来但是对于新查询的数据只要总数据大小不超过10GB前,MongoDB都不会申请额外的硬盘空间。注:上面会有一些碎片无法完全回收,即对于“每天定时删除180d前数据这个场景”磁盘利用率还是会增长的,只不过速度会慢很多。
④如果你的MongoDB允许暂停读写操作,可以在Mongo Shell中使用compact命令释放空间。命令格式如下。
db.runCommand({'compact': '集合名'})
在MongoDB 4.4之前的版本,compact会阻塞整个库的增删改查操作,所以需要业务允许暂停全部读写才能执行;在4.4版本之后,compact命令几乎可以在除了删除集合、增删索引外的任何时候执行。
注:这是一个高危的操作,一定要充分验证/慎重。。。。。
https://www.mongodb.com/docs/manual/reference/command/compact/#dbcmd.compact
一、remove和deleteMany的区别
直接抛结论:就耗时方面似乎没有什么区别!!!
二、remove操作的耗时验证
1、无systime字段索引的删除测试
(0)访问某条具体的数据
db.coll.find({systime:1600000000022002})
耗时5s
(1)删除某条具体的数据
db.coll.remove({systime:1600000000022002})
耗时5s
结论:显然这个耗时是定位这条目标数据的耗时。
(2)删除其中的50W条数据
db.coll.find({systime:{"$lte":1600000005000000}}).count()
db.coll.remove({systime:{"$lte":1600000005000000}})
#耗时19s 注:大概就是遍历用5秒+加上删除50w数据需要了14s。
结论:这个耗时应该是遍历数据用了5秒,删除50w数据用了14s。
(3)删除其中的450w条数据
db.coll.find({systime:{"$lte":1600000050000000}}).count()
db.coll.remove({systime:{"$lte":1600000050000000}})
#耗时190s 注:主要还是删除数据的耗时
2、有systime字段索引的删除测试
准备:创建systime字段的索引
db.coll.createIndex({systime:1})
(0)访问某条具体的数据
db.coll.find({systime:1600000000022002})
#瞬间 耗时1ms
(1)删除某条具体的数据
db.coll.remove({systime:1600000000022002})
#瞬间 耗时1ms
结论: 很容易理解。有索引所以瞬间定位到删除目标消息,删除的仅仅是一条所以删除操作也很快。
(2)删除其中的50W条数据
db.coll.find({systime:{"$lte":1600000005000000}}).count()
db.coll.remove({systime:{"$lte":1600000005000000}})
#耗时15s 注:应该大部分时间都是删除消耗的;
(3)删除其中的450w条数据
db.coll.find({systime:{"$lte":1600000050000000}}).count()
db.coll.remove({systime:{"$lte":1600000050000000}})
#185秒 注:systime索引带来的负担还好诶;但是也确实意义不大。
结论:如果只是一个systime字段的索引给删除带来的负担感觉还好。
3、更多索引对删除效率的影响
准备:累计创建如下四个索引
db.coll.createIndex({systime:1})
db.coll.createIndex({kfuin:1})
db.coll.createIndex({key:1})
db.coll.createIndex({key:1,systime:1})
(1)删除其中的50w条数据
db.coll.find({systime:{"$lte":1600000005000000}}).count()
db.coll.remove({systime:{"$lte":1600000005000000}})
#耗时22s 注:过多的索引确实会对删除效率产生负面影响,这个额外的三个索引是耗时变为原来的1.5倍左右。
(2)删除其中的450w条数据
db.coll.find({systime:{"$lte":1600000050000000}}).count()
db.coll.remove({systime:{"$lte":1600000050000000}})
#250秒 注:过多的索引确实会对删除效率产生负面影响,这个额外的三个索引是耗时变为原来的1.5倍左右。
结论:过多的索引确实会对删除效率造成影响;对于测试中额外多增加了三个索引删除耗时约为没有这三个索引的1.3~1.5倍左右。
4、删除操作的集群资源消耗情况
这个操作看起来主要消耗的是cpu,对于测试集群目标分片的cpu消耗在90%+。
3、结论
1、remove和deleteMany在效率上没有什么区别,以后统一用remove好了。
2、拆开来讲删除操作需要的耗时主要包括两块:①遍历到目标数据的耗时 ②删除消息的耗时。对于前者 如果删除一条数据的话就理解为定位到目标数据;如果有索引的情况下就理解为定位到需要删除数据的起始删除目标位置点。
注:oa环境低配机器无索引情况下删除100w条数据大概需要30s。
注:oa环境低配机器无索引情况下遍历100w数据需要0.5s(1000w数据5s)。
3、建立筛选字段的索引确实会对寻找删除数据、删除数据位置点都有帮助;但同时索引也会给删除操作带来负担。
4、究竟什么删除策略好还是要结合你的数据集和想要删除的数据集。
case1:数据集很大一次性删除很少的数据 —— 显然需要建立索引。
case2:数据集很大删除的数据也很多 —— 此时定位删除位置点的耗时就显得不那么重要了,主要耗时都在删除操作本身上了;极端情况下如果我们要删除目标数据集中的80%以上的数据这个时候建不建索引影响不大。
5、现在结论也比较清晰了。
(1)第一波删除的时候大概要清理其中的三分之一的数据,这个时候有systime索引更好没有感觉问题也不大(遍历的耗时和删除的耗时速度起码差了60倍)。
(2)后续如果你需要systime维度的每天删除一波过期数据应该还是要systime更好。
(3)这个时候关注重点应该是测试目标集群对一个索引后台创建索引、删除数据的资源消耗的情况,如果没问题就可以实施了。