MongoDB 的Rollback讲解及避免

前言:
     前段时间突然发现数据库文件路径下多了个rollback名字的文件夹,很是纳闷,里面居然还有.bson后缀格式的文件,随一探究竟。
原来是在这段时间内发生过failover(主从切换),导致了某些不一致数据的丢失,这可是一个大问题啊,弄不好就是要丢数据的,
故进行了些调查研究,详述于此;供大虾们大绳们指点。

解释:
     首先,rollback到底是什么意思呢?在关系型数据库中因为有事务的概念,操作数据后在没有commit之前是可以执行rollback命令进行数据回退的。
而在单实例mongodb中,写入就写入了,删除就删除了,没有事务的概念,也没有rollback的操作,所以这里要讨论的是replicaset(复制集)的rollback

事故演示:
     如下图架构,在primary上执行写操作



          
步骤1: 客户端向Primary写入3笔数据 1、2、3, 经过oplog日志后同步到secondary节点上,此时各个节点数据一致
步骤2: 但当Primary节点再次被写入一笔数据4的时候,发生宕机,此时 数据4 还没来得及同步到从节点上
步骤3: 此时集群短时间关闭写操作开始竞选,经过一系列选举后有了新的primary节点,此时新Primary节点上是没有数据4的
步骤4: 新的primary承接了客户端的write请求,写入新数据 5,此时新primary的数据状态为1,2,3,5
步骤5: 原primary节点重新启动后申请加入replica member作为secondary节点,因为此时它与新primary数据不一致,
          所以就会发生rollback(回滚)动作,将数据状态恢复为1,2,3
步骤6:回滚完之后,将继续同步新primary节点的数据,之后数据状态变为1,2,3,5

rollback发生的具体过程:
请看下图   
 
      流程说明
          客户端驱动在连接mongo之后进行写操作的大致流程就是这样的,写操作会按照编号顺序进行, 当Client收到5号的response反馈后即认为写入成功,
而如何数据已写入journal files,但是尚未oplog同步到Secondary节点重放,如果此时发生Primary宕机,则就会造成主从之间数据不一致,即原Primary中有
刚才新写入的数据,但新选举出来的Primary却没有那部分数据,从来造成数据丢失
结论:
     综上所述,rollback的发生,主要是Primary写入数据后还未来得及同步到secondary节点时,发生宕机事故,导致数据缺失,
经重新选举后产生新primary节点,但当原Primary重新加入集群时,由于要追随新Primary节点进行强一致性处理,所以会回滚宕机前未同步的数据。

存放位置:
     那么回滚的数据跑到哪里去了呢?当rollback发生时,MongoDB将把rollback的数据以BSON格式存放到dbpath路径下rollback文件夹中,BSON文件的命名格式如下:
<database>.<collection>.<timestamp>.bson
 
还原数据:
     那么这个rollback数据如何写回到mongodb呢?我们可以利用mongorestore命令进行基于文件的恢复操作,具体操作可以看我另外一篇关于mongodump/mongorestore的文章
mongorestore --host <hostname:port> --db db1 --collection c2 -u admin_user -p"123456" 
--authenticationDatabase admin rollback/c2_rollback.BSON


避免策略:
     要讲避免策略那就应先讲讲Write Concern(写关注),也就是关心写操作。是在驱动的connection level进行配置,
支持一下值:
  • w:0 | 1 | n | majority | tag
  • j:1
  • wtimeout: millis
w:0      unacknowledged
驱动只是一味的进行写入操作,不会关心是否写入成功,也就是mongo不会返回操作结果

w:1        Acknowledged
看图我们很容易理解,Driver在做写入操作后会收到mongod的反馈OK还是NG,而这个反馈行为只是在确认数据被成功写入Data Buffer,Journal Buffer
后的状态,不保证数据能够被写入datafile(落盘)

J:1          Journaled
驱动写操作不仅要写入Journal Buffer,Data Buffer中,还要确认数据持久化到Journal file中后才反馈结果。
即使数据库宕机,重启后,操作已经持久化到journal中,可以完全恢复,但前提是mongod一定要开启journal参数

  w:2/n/majority          Replica Acknowledged
看到下图,你应该就明白一半了,好的,下一半让小弟再给你解释一下。
rollback的发生就是因为数据成功写入Primary,但是尚未同步到Secondary节点,此时Primary宕机,
当原Primary重新加入集群后则会发生灰色数据自行rollback的现象,那么怎么避免呢?当然就是在发送反馈信息给驱动前
确保数据已经更新到至少一个Secondary节点,不就完美解决此问题了。是的,使用w:2/n/majority的配置参数
就能实现,当然,为了防止网络问题出现阻塞等待,我们可以设置wtimeout

 
 Rollback 的限制:
官网有这样一句话:

我的理解是rollback的数据超过300M后就需要手动干预从原Primary去除灰色数据了,但是有网友测试3G多的数据也能自行rollback,
So,I'm not clear the real meaning of the Limitations

 





<think>好的,我现在需要帮助用户了解在Python测试环境中初始化数据库的方法。首先,我需要回忆用户提供的引用内容,看看是否有相关的信息可以利用。根据引用[1],提到了使用PyMySQL操作MySQL数据库,引用[3]则提到了初始化MongoDB的连接。用户的问题是关于测试环境中初始化数据库的方法,所以可能需要涵盖不同的数据库类型,比如MySQL和MongoDB,以及如何在这些环境中进行初始化操作。 首先,我应该明确初始化数据库在测试中的目的,通常包括准备测试数据、创建临时数据库或表、以及测试后的清理工作。接下来,需要分步骤说明如何实现这些操作。对于不同的数据库,方法可能有所不同,所以可能需要分别介绍。 对于MySQL,可以使用PyMySQL库。初始化步骤可能包括连接数据库、创建测试数据库或表、插入测试数据,然后在测试后删除或回滚。需要确保每次测试前数据库处于干净的状态,避免测试之间的相互影响。例如,在setUp方法中创建连接并初始化数据,在tearDown中关闭连接并清理数据。 对于MongoDB,引用[3]提到了使用pymongo库,初始化可能涉及连接到数据库,并在测试前清空集合或插入初始数据。同样,测试后需要清理数据,保持环境干净。 另外,使用测试框架如unittest或pytest的结构会更规范。在测试用例类中,setUpClass和tearDownClass可以用于整个测试类的初始化和清理,而setUp和tearDown则针对每个测试方法。这样可以提高效率,避免重复代码。 还需要考虑事务回滚,特别是在支持事务的数据库中,如MySQL的InnoDB引擎。通过开启事务,在测试后回滚,可以自动撤销所有更改,保持数据库状态不变。但要注意存储引擎是否支持事务。 此外,使用临时数据库或Docker容器也是一种方法,尤其是在需要隔离测试环境时。例如,使用Docker在测试时启动一个临时数据库实例,测试结束后销毁容器,确保环境独立。 现在需要将这些思路整理成结构清晰的步骤,并给出代码示例。例如,针对MySQL和MongoDB分别展示初始化方法,说明事务回滚和临时数据库的使用。同时,结合测试框架的结构,展示如何在setUp和tearDown中处理数据库连接和数据。 还要注意用户提到的引用,引用[1]中的PyMySQL操作,引用[3]中的MongoDB连接初始化,需要正确引用这些内容。比如在讲解MySQL时提到引用[1],在MongoDB部分提到引用[3]。 最后,生成相关问题,帮助用户进一步了解数据库测试的其他方面,比如如何管理测试数据、隔离测试环境、使用ORM工具等。</think>在Python测试环境中初始化数据库的常用方法如下,分为基础操作和进阶优化方案: ### 一、基础初始化方法 1. **使用PyMySQL操作MySQL数据库**[^1] ```python import pymysql class TestDatabase(unittest.TestCase): @classmethod def setUpClass(cls): cls.conn = pymysql.connect(host='localhost', user='root', password='123456') cls.cursor = cls.conn.cursor() cls.cursor.execute("CREATE DATABASE IF NOT EXISTS test_db") cls.cursor.execute("USE test_db") cls.cursor.execute("CREATE TABLE users(id INT PRIMARY KEY, name VARCHAR(20))") def test_insert(self): self.cursor.execute("INSERT INTO users VALUES (1, 'Alice')") result = self.cursor.execute("SELECT * FROM users") self.assertEqual(result, 1) @classmethod def tearDownClass(cls): cls.cursor.execute("DROP DATABASE test_db") cls.conn.close() ``` 2. **使用pymongo操作MongoDB**[^3] ```python from pymongo import MongoClient class TestMongoDB(unittest.TestCase): def setUp(self): self.client = MongoClient('mongodb://localhost:27017/') self.db = self.client['test_db'] self.collection = self.db.create_collection('test_data') def test_insert(self): self.collection.insert_one({'_id': 1, 'value': 'test'}) self.assertEqual(self.collection.count_documents({}), 1) def tearDown(self): self.client.drop_database('test_db') ``` ### 二、进阶优化方案 1. **事务回滚(MySQL示例)** ```python with self.conn.cursor() as cursor: self.conn.begin() # 开启事务 cursor.execute("INSERT INTO logs VALUES ('test_log')") # 测试断言... self.conn.rollback() # 自动撤销变更 ``` 2. **使用临时数据库容器(Docker+Python)** ```python import docker client = docker.from_env() container = client.containers.run( "mysql:latest", environment={"MYSQL_ROOT_PASSWORD": "123456"}, ports={'3306/tcp': 3306}, detach=True ) # 执行测试... container.stop() # 测试结束后销毁容器 ``` ### 三、最佳实践建议 1. 使用`setUpClass`/`tearDownClass`管理类级别连接 2. 通过环境变量配置数据库连接参数 3. 采用工厂模式创建测试数据 4. 使用`unittest.mock`模拟数据库操作
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值