3.MongoDB恢复探究:为什么oplogReplay参数只设置了日志应用结束时间oplogLimit,而没有设置开始时间?

3.MongoDB恢复探究:为什么oplogReplay参数只设置了日志应用结束时间oplogLimit,而没有设置开始时间?,第1张

概述(一)我的疑问在使用MySQL数据库binlog日志基于时间点恢复数据库时,我们必须要指定binlog的开始位置和结束位置,而在MongoDB里面,如果使用oplog进行恢复,只有oplogLimit

(一)我的疑问

在使用MysqL数据库binlog日志基于时间点恢复数据库时,我们必须要指定binlog的开始位置和结束位置,而在MongoDB里面,如果使用oplog进行恢复,只有oploglimit参数,该参数信息如下

--oploglimit=<seconds>[:ordinal]          only include oplog entrIEs before the provIDed Timestamp

oploglimit参数定义了数据库恢复到该时间点。也就是说,MongoDB只是设置了oplog的结束位置,没有指定oplog的开始位置。那么就存在问题了,以下图为例,我在T3时刻执行了全备份,在T4时刻数据库发生了误 *** 作,当我执行恢复的时候,分为2个步骤:

阶段1:使用完全备份,将数据库恢复到T3时刻;阶段2:使用oplog日志,将数据库恢复到T4故障之前。T4故障之前的时间点由参数oploglimit控制,但是:oplog的开始时间不是从T3时刻,而是T2时刻,这里T2是oplog记录的最早时间,该时间并不受我们控制。

补充:这里的“不受我们控制”是指在使用mongorestore重做oplog的时候,我们没办法指定开始时间。但是如果想要把oplog的开始时间控制在T3时刻,还是有办法的:使用bsondump分析全备的最后一笔数据,在备份oplog的时候,用query选项过滤掉之前的数据即可。然而,这并不是我们关心的,我所关心的,是为什么mongorestore不给出恢复 *** 作的开始时间参数。

说了那么多,把问题明确一下:

mongorestore在恢复oplog的时候,只限定了日志的结束位置,而没有开始位置,这样就会造成oplog恢复的开始位置不是T3,而是在T2,那么就会存在T2~T3这段时间数据重复 *** 作的问题,理论上会造成数据变化,为什么mongorestore不设定一个开始时间参数去避免重复 *** 作的问题呢?

本次测试在mongodb 4.2 副本集环境下进行。


(二)问题探索

(2.1)oplog日志格式解析

既然该问题可能会发生在重做oplog时,那么我们不妨先看一下oplog到底存储了什么信息。为了查看oplog日志保存了什么信息,向test集合中插入1条数据:

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

使用下面查询语句查看oplog日志信息:

use local db.oplog.rs.find( { $and : [ {"ns" : "testdb.test"} ] } ).sort({ts:1})

结果如下:

{ "ts" : Timestamp(1597070283,1),"op" : "i","ns" : "lijiamandb.test","o" : { "_ID" : ObjectID("5f30eb58bcefe5270574cd54"),kungming" }}

oplog中各个字段的含义:

ts:数据写的时间,括号里面第1位数据代表时间戳,是自unix纪元以来的秒值,第2位代表在1s内订购时间戳的序列数op: *** 作类型,可选参数有:

       -- "i": insert

       --"u": update

       --"d": delete

       --"c": db cmd

       --"db":声明当前数据库 (其中ns 被设置成为=>数据库名称+ '.')

       --"n": no op,即空 *** 作,其会定期执行以确保时效性

ns:命名空间,通常是具体的集合o:具体的写入信息o2: 在执行更新 *** 作时的where条件,仅限于update时才有该属性


(2.2)文档中的“_ID”字段

在上面的插入文档中,我们发现每插入一个文档,都会伴随着产生一个“_ID”字段,该字段是一个object类型,对于“_ID”,需要知道:

"_ID"是集合文档的主键,每个文档(即每行记录)都有一个唯一的"_ID"值"_ID"会自动生成,也可以手动指定,但是必须唯一且非空。


经过测试,发现在执行文档的DML *** 作时,会根据ID进行,我们不妨来看看DML *** 作的文档变化。

(1)插入文档,查看文档信息与oplog信息

use testdb//插入文档db.mycol.insert({ID:23456PRIMARY> db.mycol.find(){ "_ID" : ObjectID("5f3b471a6530eb8aa5bf88a0"),"ID" : 6,"name" : "f" }

这里记录该集合文档的变化,可以发现,mongodb为每条数据都分配了一个唯一且非空的”_ID”:

此时查看oplog,如下

1597720346
,"t" : NumberLong(
110-4a15-44ca-96c8-3b3b5be29616"),"wall" : ISODate("2020-08-18T03:12:26.231Z"),"o" : { "_ID" : ObjectID("5f3b471a6530eb8aa5bf88a0"),"name" : "a" }} 2 .246Z"),"o" : { "_ID" : ObjectID("5f3b471a6530eb8aa5bf88a1"),1); Font-weight: bold">2.0


(2)更新 *** 作

rstest:> db.mycol.update({"ID":1},{$set:{"name":"aa"}})WriteResult({ "nMatched" : 0,"nModifIEd" : 1 })

这里更新了1行数据,可以看到,文档ID是没有发生变化的

此时查看oplog,如下:

7
1597720412,"op" : "u","o2" : { "_ID" : ObjectID("5f3b471a6530eb8aa5bf88a0") },1); Font-weight: bold">13
:32.649Z"),"o" : { "$v" : " : { "name" : "aa" } }}

这里值得我们注意:上面我们说到,oplog的”o2”参数是更新的where条件,我们在执行更新的时候,指定的where条件是”ID=1”,ID是我们自己定义的列,然而,在oplog里面指定的where条件是

"_ID" : ObjectID("5f3b471a6530eb8aa5bf88a0"),很明显,他们都指向了同一条数据。这样,当我们使用oplog进行数据恢复的时候,直接根据”_ID”去做数据更新,即使再执行N遍,也不会导致数据更新出错。


(3)再次更新 *** 作

上面我们是对某一条数据进行更新,并且在update中指出了更新后的数据,这里再测试一下,我使用自增的方式更新数据。

// 每条数据的ID在当前的基础上加10
rstest:update({},{$inc:{"ID":10}},{multi:true}) WriteResult({ "nMatched" : 6 })

数据变化如图,可以看到,ID虽然发生了变化,但是”_ID”是没有改变的。

再来看oplog信息

8
1597720424,1); Font-weight: bold">44
.398Z"),1)">" : { "ID" : 11.0 } }} 9 .399Z"),1); Font-weight: bold">12.0 10 13.0 11 @H_43_403@.400Z"),1); Font-weight: bold">14.0
12 15.0 13 16.0 } }}

这里也非常值得我们注意:o2记录的是已经发生更改的文档_ID,o就比较有意思了,记录的是发生变更之后的值。我们可以发现,如果我们把上面自增更新的sql执行每执行1次,ID都会加10,但是,我们重复执行N次oplog,并不会改变对应记录的值。


(4)再来看看删除 *** 作

// 删除ID大于14的条目
rstest:> db.mycol.remove({"ID":{"$gt":14}}) WriteResult({ "nRemoved" : 2 })

数据变化如下图:

再来看看oplog日志:

14
1597720485,"op" : "d",1); Font-weight: bold">14
:45.511Z"),"o" : { "_ID" : ObjectID("5f3b471a6530eb8aa5bf88a4") }} 15

”op”:”d”选项记录了该 *** 作是执行删除,具体删除什么数据,由o选项记录,可以看到,o记录的是”_ID”,也就是说,oplog中删除 *** 作是根据”_ID”执行的。


(三)结论

可以看到,在DML *** 作数据库时,oplog时基于"_ID"记录文档变化的。那么,我们来总结一下开头提出的问题:未指定开始时间,oplog数据是否会重复 *** 作呢?

如果当前数据库已经存在相同ID的数据,那么不会执行二次插入,主键冲突报错;在做更新时,记录的是更新文档的"_ID"以及发生变更后的数据,因此,如果再次执行,只会修改该条数据,哪怕执行N遍,效果也和执行一遍是一样的,所有也就不怕重复 *** 作单条数据了;在执行删除 *** 作时,记录的是删除的文档"_ID",同样,执行N遍和执行一遍效果是一样的,因为”_ID”是唯一的。

因此,即使oplog从完全备份之前开始应用,也不会造成数据的多次变更。


【完】

相关文档:

1.MongoDB 2.7主从复制(master –> slave)环境基于时间点的恢复  
2.MongoDB 4.2副本集环境基于时间点的恢复


3.MongoDB恢复探究:为什么oplogReplay参数只设置了日志应用结束时间oplogLimit,而没有设置开始时间?

@H_955_502@
总结

以上是内存溢出为你收集整理的3.MongoDB恢复探究:为什么oplogReplay参数只设置了日志应用结束时间oplogLimit,而没有设置开始时间?全部内容,希望文章能够帮你解决3.MongoDB恢复探究:为什么oplogReplay参数只设置了日志应用结束时间oplogLimit,而没有设置开始时间?所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/sjk/1152060.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-31
下一篇2022-05-31

发表评论

登录后才能评论

评论列表(0条)

    保存