第十九天
今天要学习的章节是《22 | 事务开发:多文档事务》,主要讲解多文档事务管理。
说明
- 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
事务的隔离级别
- 事务完成前,事务外的操作对该事务所做的修改不可访问
- 如果事务内使用 {readConcern: “snapshot”},则可以达到可重复读Repeatable Read
事务隔离
即事务内的变化没提交前不影响事务外的数据
db.tx.insertMany([{ x: 1 }, { x: 2 }]);
var session = db.getMongo().startSession();
session.startTransaction();
var coll = session.getDatabase('test').getCollection("tx");
coll.updateOne({x: 1}, {$set: {y: 1}});
coll.findOne({x: 1}); // 返回 {x:1, y:1}
db.tx.findOne({x: 1}); // 返回 {x:1}
session.abortTransaction()
可重复读
事务外虽然数据已经更新,在事务内结束前,读取的数据一致是相同的。
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}
db.tx.updateOne({x: 1}, {$set: {y: 1}});
db.tx.findOne({x: 1}); // 返回:{x: 1, y: 1}
coll.findOne({x: 1}); // 返回:{x: 1}
session.abortTransaction();
事务写机制
MongoDB 的事务错误处理机制不同于关系数据库:
- 当一个事务开始后,如果事务要修改的文档在事务外部被修改过,则事务修改这个文档时会触发 Abort 错误,因为此时的修改冲突了;
- 这种情况下,只需要简单地重做事务,也就是把事务中止了,重新开始;
- 如果一个事务已经开始修改一个文档,在事务以外尝试修改同一个文档,则事务以外的修改会等待事务完成才能继续进行。
实验:写冲突
var session = db.getMongo().startSession();
session.startTransaction({ readConcern: {level: "snapshot"},
writeConcern: {w: "majority"}});
var coll = session.getDatabase('test').getCollection("tx");
继续使用上个实验的tx集合,开两个 mongo shell 均执行下述语句
窗口1:coll.updateOne({x: 1}, {$set: {y: 1}}); // 正常结束
窗口2:coll.updateOne({x: 1}, {$set: {y: 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 只应该在事务级别设置,不能设置在每次读写操作上。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。