最近做数据备份的时候发现了有个很严重的问题,那就是数据丢失(最后证明没丢,是别的问题造成的)。
问题如下:
我通过两种方式在两个mongoDB集群中,对一组collection进行备份,最后2个备份数据的数据个数不相同,并且都小于原始collection的count结果。于是便开始寻求解决办法,流程如下:
1、记录3组数据,原始数据集按条件count有909217个数据,备份代码如下,其中replaceOne备份下来的数据有907582条,而使用insertMany备份下来的结果有906281条(注释是之后加的,之前用的是insertMany):
rdd.foreachPartition { x => { val mongoURI = new MongoClientURI(uri) val mongo = new MongoClient(mongoURI) val db = mongo.getDatabase("wenshu") val dbColl = db.getCollection("testbackup") val mongoURI2 = new MongoClientURI(uri2) val mongo2 = new MongoClient(mongoURI2) val db2 = mongo2.getDatabase("wenshu") val dbColl2 = db2.getCollection(backName) var count = 0 var resList = new ArrayList[Document] x.foreach(y => { // count = count + 1 // resList add y try{ dbColl.replaceOne(eqq("_id", y.get("_id")), y, new UpdateOptions().upsert(true)) dbColl2.insertOne(y) }catch{ case e: Throwable => e.printStackTrace() } // 使用这种方式插入会导致插入的数据和真实数据数量对应不上, 先注释掉有机会再找原因 // if (count == 10000){ // try{ // dbColl2.insertMany(resList, new InsertManyOptions().ordered(false)) // }catch{ // case e: Throwable => e.printStackTrace() // } // resList.clear // count = 0 // } }) // if (count > 0) // try{ // dbColl2.insertMany(resList, new InsertManyOptions().ordered(false)) // }catch{ // case e: Throwable => e.printStackTrace() // }
2、通过查询stackoverflow和jira发现数据丢失问题曾经存在过,但都是2.0之前的mongodb,现在商用化之后的mongodb基本没人出现过数据丢失问题。
3、检查代码,发现不是插入代码错误。
4、对抽出来的907582条数据的库进行备份,还是用上述程序,发现replaceOne的数据有907582,而insertmany只有904291条数据。
5、结合上述条件,推测是insertMany导致部分数据丢失,所以才会出现insertMany结果和replaceOne不一样。
6、对此结论进行测试,将insertMany改为上述代码中的insertOne重新备份907582条数据。
7、结果正确,重新备份下来的2份数据都是907582条。目前解决了其中一个问题,就是备份出来的两份数据不一样多的问题,接下来考虑备份数据和从总库中抽取的数据不一致的问题。
8、对mongo shell的count操作查找其工作原理,发现有一些报告count数据不准的问题,结合自身原因推测是count的问题,数据应该只有907582条。
9、通过多抽取几遍对这个问题进行测试,按同样条件抽了3遍返回的结果都是907582条,可以认定数据库中只有907582条满足此条件的数据。
结论:
1、MongoDB的Count操作有可能返回错误的结果。至少在Sharding Cluster,多个索引和2级索引的条件下会出现这种问题。
2、插入时不要使用InsertMany,会导致数据丢失。
3、同理,尽量不要使用updateMany,虽然不会导致数据丢失,但是按照结论2推测有可能出现某些数据更新失败的情况。