(一)个人疑问html
在使用MySQL数据库binlog日志基于时间点恢复数据库时,咱们必需要指定binlog的开始位置和结束位置,而在MongoDB里面,若是使用oplog进行恢复,只有oplogLimit参数,该参数信息以下mongodb
--oplogLimit=<seconds>[:ordinal] only include oplog entries before the provided Timestamp
oplogLimit参数定义了数据库恢复到该时间点。也就是说,MongoDB只是设置了oplog的结束位置,没有指定oplog的开始位置。那么就存在问题了,如下图为例,我在T3时刻执行了全备份,在T4时刻数据库发生了误操做,当我执行恢复的时候,分为2个步骤:数据库
补充:这里的“不受咱们控制”是指在使用mongorestore重作oplog的时候,咱们没办法指定开始时间。可是若是想要把oplog的开始时间控制在T3时刻,仍是有办法的:使用bsondump分析全备的最后一笔数据,在备份oplog的时候,用query选项过滤掉以前的数据便可。然而,这并非咱们关心的,我所关心的,是为何mongorestore不给出恢复操做的开始时间参数。ide
说了那么多,把问题明确一下:测试
mongorestore在恢复oplog的时候,只限定了日志的结束位置,而没有开始位置,这样就会形成oplog恢复的开始位置不是T3,而是在T2,那么就会存在T2~T3这段时间数据重复操做的问题,理论上会形成数据变化,为何mongorestore不设定一个开始时间参数去避免重复操做的问题呢?ui
本次测试在mongodb 4.2 副本集环境下进行。spa
(二)问题探索3d
(2.1)oplog日志格式解析unix
既然该问题可能会发生在重作oplog时,那么咱们不妨先看一下oplog到底存储了什么信息。为了查看oplog日志保存了什么信息,向test集合中插入1条数据:rest
db.test.insert({"empno":1,"ename":"lijiaman","age":22,"address":"yunnan,kungming"});
查看test集合的数据信息
db.test.find() /* 1 */ { "_id" : ObjectId("5f30eb58bcefe5270574cd54"), "empno" : 1.0, "ename" : "lijiaman", "age" : 22.0, "address" : "yunnan,kungming" }
使用下面查询语句查看oplog日志信息:
use local db.oplog.rs.find( { $and : [ {"ns" : "testdb.test"} ] } ).sort({ts:1})
结果以下:
/* 1 */ { "ts" : Timestamp(1597070283, 1), "op" : "i", "ns" : "lijiamandb.test", "o" : { "_id" : ObjectId("5f30eb58bcefe5270574cd54"), "empno" : 1.0, "ename" : "lijiaman", "age" : 22.0, "address" : "yunnan,kungming" } }
oplog中各个字段的含义:
-- "i": insert
--"u": update
--"d": delete
--"c": db cmd
--"db":声明当前数据库 (其中ns 被设置成为=>数据库名称+ '.')
--"n": no op,即空操做,其会按期执行以确保时效性
(2.2)文档中的“_id”字段
在上面的插入文档中,咱们发现每插入一个文档,都会伴随着产生一个“_id”字段,该字段是一个object类型,对于“_id”,须要知道:
通过测试,发如今执行文档的DML操做时,会根据ID进行,咱们不妨来看看DML操做的文档变化。
(1)插入文档,查看文档信息与oplog信息
use testdb //插入文档 db.mycol.insert({id:1,name:"a"}) db.mycol.insert({id:2,name:"b"}) db.mycol.insert({id:3,name:"c"}) db.mycol.insert({id:4,name:"d"}) db.mycol.insert({id:5,name:"e"}) db.mycol.insert({id:6,name:"f"}) rstest:PRIMARY> db.mycol.find() { "_id" : ObjectId("5f3b471a6530eb8aa5bf88a0"), "id" : 1, "name" : "a" } { "_id" : ObjectId("5f3b471a6530eb8aa5bf88a1"), "id" : 2, "name" : "b" } { "_id" : ObjectId("5f3b471a6530eb8aa5bf88a2"), "id" : 3, "name" : "c" } { "_id" : ObjectId("5f3b471a6530eb8aa5bf88a3"), "id" : 4, "name" : "d" } { "_id" : ObjectId("5f3b471a6530eb8aa5bf88a4"), "id" : 5, "name" : "e" } { "_id" : ObjectId("5f3b471b6530eb8aa5bf88a5"), "id" : 6, "name" : "f" }
这里记录该集合文档的变化,能够发现,mongodb为每条数据都分配了一个惟一且非空的”_id”:
此时查看oplog,以下
/* 1 */ { "ts" : Timestamp(1597720346, 2), "t" : NumberLong(11), "h" : NumberLong(0), "v" : 2, "op" : "i", "ns" : "testdb.mycol", "ui" : UUID("56c4e1ad-4a15-44ca-96c8-3b3b5be29616"), "wall" : ISODate("2020-08-18T03:12:26.231Z"), "o" : { "_id" : ObjectId("5f3b471a6530eb8aa5bf88a0"), "id" : 1.0, "name" : "a" } } /* 2 */ { "ts" : Timestamp(1597720346, 3), "t" : NumberLong(11), "h" : NumberLong(0), "v" : 2, "op" : "i", "ns" : "testdb.mycol", "ui" : UUID("56c4e1ad-4a15-44ca-96c8-3b3b5be29616"), "wall" : ISODate("2020-08-18T03:12:26.246Z"), "o" : { "_id" : ObjectId("5f3b471a6530eb8aa5bf88a1"), "id" : 2.0, "name" : "b" } } ... 略 ...
(2)更新操做
rstest:PRIMARY> db.mycol.update({"id":1},{$set:{"name":"aa"}}) WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
此时查看oplog,以下:
/* 7 */ { "ts" : Timestamp(1597720412, 1), "t" : NumberLong(11), "h" : NumberLong(0), "v" : 2, "op" : "u", "ns" : "testdb.mycol", "ui" : UUID("56c4e1ad-4a15-44ca-96c8-3b3b5be29616"), "o2" : { "_id" : ObjectId("5f3b471a6530eb8aa5bf88a0") }, "wall" : ISODate("2020-08-18T03:13:32.649Z"), "o" : { "$v" : 1, "$set" : { "name" : "aa" } } }
这里值得咱们注意:上面咱们说到,oplog的”o2”参数是更新的where条件,咱们在执行更新的时候,指定的where条件是”id=1”,id是咱们本身定义的列,然而,在oplog里面指定的where条件是
"_id" : ObjectId("5f3b471a6530eb8aa5bf88a0"),很明显,他们都指向了同一条数据。这样,当咱们使用oplog进行数据恢复的时候,直接根据”_id”去作数据更新,即便再执行N遍,也不会致使数据更新出错。
(3)再次更新操做
上面咱们是对某一条数据进行更新,而且在update中指出了更新后的数据,这里再测试一下,我使用自增的方式更新数据。
// 每条数据的id在当前的基础上加10rstest:PRIMARY> db.mycol.update({},{$inc:{"id":10}},{multi:true}) WriteResult({ "nMatched" : 6, "nUpserted" : 0, "nModified" : 6 })
数据变化如图,能够看到,id虽然发生了变化,可是”_id”是没有改变的。
再来看oplog信息
/* 8 */ { "ts" : Timestamp(1597720424, 1), "t" : NumberLong(11), "h" : NumberLong(0), "v" : 2, "op" : "u", "ns" : "testdb.mycol", "ui" : UUID("56c4e1ad-4a15-44ca-96c8-3b3b5be29616"), "o2" : { "_id" : ObjectId("5f3b471a6530eb8aa5bf88a0") }, "wall" : ISODate("2020-08-18T03:13:44.398Z"), "o" : { "$v" : 1, "$set" : { "id" : 11.0 } } } /* 9 */ { "ts" : Timestamp(1597720424, 2), "t" : NumberLong(11), "h" : NumberLong(0), "v" : 2, "op" : "u", "ns" : "testdb.mycol", "ui" : UUID("56c4e1ad-4a15-44ca-96c8-3b3b5be29616"), "o2" : { "_id" : ObjectId("5f3b471a6530eb8aa5bf88a1") }, "wall" : ISODate("2020-08-18T03:13:44.399Z"), "o" : { "$v" : 1, "$set" : { "id" : 12.0 } } } /* 10 */ { "ts" : Timestamp(1597720424, 3), "t" : NumberLong(11), "h" : NumberLong(0), "v" : 2, "op" : "u", "ns" : "testdb.mycol", "ui" : UUID("56c4e1ad-4a15-44ca-96c8-3b3b5be29616"), "o2" : { "_id" : ObjectId("5f3b471a6530eb8aa5bf88a2") }, "wall" : ISODate("2020-08-18T03:13:44.399Z"), "o" : { "$v" : 1, "$set" : { "id" : 13.0 } } } /* 11 */ { "ts" : Timestamp(1597720424, 4), "t" : NumberLong(11), "h" : NumberLong(0), "v" : 2, "op" : "u", "ns" : "testdb.mycol", "ui" : UUID("56c4e1ad-4a15-44ca-96c8-3b3b5be29616"), "o2" : { "_id" : ObjectId("5f3b471a6530eb8aa5bf88a3") }, "wall" : ISODate("2020-08-18T03:13:44.400Z"), "o" : { "$v" : 1, "$set" : { "id" : 14.0 } } } /* 12 */ { "ts" : Timestamp(1597720424, 5), "t" : NumberLong(11), "h" : NumberLong(0), "v" : 2, "op" : "u", "ns" : "testdb.mycol", "ui" : UUID("56c4e1ad-4a15-44ca-96c8-3b3b5be29616"), "o2" : { "_id" : ObjectId("5f3b471a6530eb8aa5bf88a4") }, "wall" : ISODate("2020-08-18T03:13:44.400Z"), "o" : { "$v" : 1, "$set" : { "id" : 15.0 } } } /* 13 */ { "ts" : Timestamp(1597720424, 6), "t" : NumberLong(11), "h" : NumberLong(0), "v" : 2, "op" : "u", "ns" : "testdb.mycol", "ui" : UUID("56c4e1ad-4a15-44ca-96c8-3b3b5be29616"), "o2" : { "_id" : ObjectId("5f3b471b6530eb8aa5bf88a5") }, "wall" : ISODate("2020-08-18T03:13:44.400Z"), "o" : { "$v" : 1, "$set" : { "id" : 16.0 } } }
这里也很是值得咱们注意:o2记录的是已经发生更改的文档_id,o就比较有意思了,记录的是发生变动以后的值。咱们能够发现,若是咱们把上面自增更新的SQL执行每执行1次,id都会加10,可是,咱们重复执行N次oplog,并不会改变对应记录的值。
(4)再来看看删除操做
// 删除id大于14的条目
rstest:PRIMARY> db.mycol.remove({"id":{"$gt":14}}) WriteResult({ "nRemoved" : 2 })
数据变化以下图:
再来看看oplog日志:
/* 14 */ { "ts" : Timestamp(1597720485, 1), "t" : NumberLong(11), "h" : NumberLong(0), "v" : 2, "op" : "d", "ns" : "testdb.mycol", "ui" : UUID("56c4e1ad-4a15-44ca-96c8-3b3b5be29616"), "wall" : ISODate("2020-08-18T03:14:45.511Z"), "o" : { "_id" : ObjectId("5f3b471a6530eb8aa5bf88a4") } } /* 15 */ { "ts" : Timestamp(1597720485, 2), "t" : NumberLong(11), "h" : NumberLong(0), "v" : 2, "op" : "d", "ns" : "testdb.mycol", "ui" : UUID("56c4e1ad-4a15-44ca-96c8-3b3b5be29616"), "wall" : ISODate("2020-08-18T03:14:45.511Z"), "o" : { "_id" : ObjectId("5f3b471b6530eb8aa5bf88a5") } }
”op”:”d”选项记录了该操做是执行删除,具体删除什么数据,由o选项记录,能够看到,o记录的是”_id”,也就是说,oplog中删除操做是根据”_id”执行的。
(三)结论
能够看到,在DML操做数据库时,oplog时基于"_id"记录文档变化的。那么,咱们来总结一下开头提出的问题:未指定开始时间,oplog数据是否会重复操做呢?
所以,即便oplog从彻底备份以前开始应用,也不会形成数据的屡次变动。
【完】
相关文档: 1.MongoDB 2.7主从复制(master –> slave)环境基于时间点的恢复
|