MongoDB 的 ACID 事务支持

来自AI助手的总结
MongoDB支持多文档事务,但应尽量少用,关注隔离性与写冲突机制
MongoDB 的 ACID 事务支持

一、介绍

MongoDB 虽然已经在 4.2 开始全面支持了多文档事务,但并不代表大家应该毫无节制地使用它。相反,对事务的使用原则应该是:能不用尽量不用。

为什么不建议使用?

事务 = 锁,节点协调,额外开销,性能影响。

通过合理地设计文档模型,可以规避绝大部分使用事务的必要性

二、MongoDB ACID 多文档事务支持

事务属性 支持程度
Atomocity 原子性 单表单文档:1.x 就支持;复制集多表多行:4.0 复制集;分片集群多表多行:4.2
Consistency 一致性 writeConcern, readConcern (3.2)
Isolation 隔离性 readConcern (3.2)
Durability 持久性 Journal and Replication

三、使用方法

MongoDB 多文档事务的使用方式与关系数据库非常相似:

try (ClientSession clientSession = client.startSession()) {
  clientSession.startTransaction();
  collection.insertOne(clientSession, docOne);
  collection.insertOne(clientSession, docTwo);
  clientSession.commitTransaction();
}
  • 四、事务的隔离级别

  • 事务完成前,事务外的操作对该事务所做的修改不可访问

  • 如果事务内使用 {readConcern: “snapshot”},则可以达到可重复读 Repeatable Read

四、实验:启用事务后的隔离性


repl:PRIMARY> db.tx.insertMany([{ x: 1 }, { x: 2 }]);

repl:PRIMARY> db.tx.find()

{ "_id" : ObjectId("635bc84764d7be2f932c3a0d"), "x" : 1 }

{ "_id" : ObjectId("635bc84764d7be2f932c3a0e"), "x" : 2 }

repl:PRIMARY> var session = db.getMongo().startSession();

repl:PRIMARY> session.startTransaction();

repl:PRIMARY> var coll = session.getDatabase('test').getCollection("tx");

repl:PRIMARY> coll.updateOne({x: 1}, {$set: {y: 1}});  //事务内操作将 x=1改为 y=1

repl:PRIMARY> coll.find()   //事务内查询

{ "_id" : ObjectId("635bc84764d7be2f932c3a0d"), "x" : 1, "y" : 1 }

{ "_id" : ObjectId("635bc84764d7be2f932c3a0e"), "x" : 2 }

repl:PRIMARY> db.tx.find()   //事务外查询

{ "_id" : ObjectId("635bc84764d7be2f932c3a0d"), "x" : 1 }

{ "_id" : ObjectId("635bc84764d7be2f932c3a0e"), "x" : 2 }

repl:PRIMARY> session.commitTransaction();  //提交事务(或者 s.abortTransaction()回滚事务)

repl:PRIMARY> db.tx.find()

{ "_id" : ObjectId("635bc9e964d7be2f932c3a0f"), "x" : 1, "y" : 1 }

{ "_id" : ObjectId("635bc9e964d7be2f932c3a10"), "x" : 2 }

五、实验:可重复读 Repeatable Read


db.tx.insertMany([{ x: 1 }, { x: 2 }]);

db.tx.find()

{ "_id" : ObjectId("635bc84764d7be2f932c3a0d"), "x" : 1 }

{ "_id" : ObjectId("635bc84764d7be2f932c3a0e"), "x" : 2 }

var session = db.getMongo().startSession();

session.startTransaction({readConcern: {level: "snapshot"},writeConcern: {w: "majority"}});

var coll = session.getDatabase('test').getCollection("tx");

coll.findOne({x: 1});  // 事务内,返回: {x: 1}

{ "_id" : ObjectId("635bcc1164d7be2f932c3a11"), "x" : 1 }

db.tx.updateOne({x: 1}, {$set: {y: 1}});   //事务外更新

db.tx.findOne({x: 1}); // 事务外,返回: {x: 1, y: 1}

{ "_id" : ObjectId("635bcc1164d7be2f932c3a11"), "x" : 1, "y" : 1 }

coll.findOne({x: 1}); // 事务内,返回: {x: 1}

{ "_id" : ObjectId("635bcc1164d7be2f932c3a11"), "x" : 1 }

session.commitTransaction();  // 提交

db.tx.findOne({x: 1});

{ "_id" : ObjectId("635bcc1164d7be2f932c3a11"), "x" : 1, "y" : 1 } //这是比较高的一个事务隔离性

六、事务写机制

MongoDB 的事务错误处理机制不同于关系数据库:

  • 当一个事务开始后,如果事务要修改的文档在事务外部被修改过,则事务修改这个文档时会触发 Abort 错误,因为此时的修改冲突了;

  • 这种情况下,只需要简单地重做事务就可以了;

  • 如果一个事务已经开始修改一个文档,在事务以外尝试修改同一个文档,则事务以外的修改会等待事务完成才能继续进行

写冲突实验:

(1) 实验1,2个窗口测试事务内的更新

–继续使用上个实验的 tx 集合,开两个 mongo shell 均执行下述语句


var session = db.getMongo().startSession();

session.startTransaction({ readConcern: {level: "snapshot"},writeConcern: {w: "majority"}});

var coll = session.getDatabase('test').getCollection("tx");

(2) 实验2,事务外更新


窗口1:第一个事务,正常提交

coll.updateOne({x: 1}, {$set: {y: 1}});

窗口2:另一个事务更新同一条数据,异常

coll.updateOne({x: 1}, {$set: {y: 2}});

窗口3:事务外更新,需等待

db.tx.updateOne({x: 1}, {$set: {y: 3}});

  • 八、注意事项

  • 可以实现和关系型数据库类似的事务场景

  • 必须使用与 MongoDB 4.2 兼容的驱动;

  • 事务默认必须在 60 秒(可调)内完成,否则将被取消;

  • 涉及事务的分片不能使用仲裁节点;

  • 事务会影响 chunk 迁移效率。正在迁移的 chunk 也可能造成事务提交失败(重试即可);

  • 多文档事务中的读操作必须使用主节点读;

  • readConcern 只应该在事务级别设置,不能设置在每次读写操作上。

  • 必须是 WT 引擎才支持事务。

© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容