MongoDB 事务

前言

在 MongoDB 中,对单个文档的操作都是原子的。因为可以在单个文档结构中使用内嵌文档和数据获得数据之间的关系,所以不必跨多个文档和集合进行范式化,这种

结构特性,避免了很多场景中的对多文档事务的需求。

对于需要多个文档进行原子读写的场景,MongoDB 中引入了多文档事务和分布式事务。

  • 在4.0版本中,MongoDB支持副本集上的多文档事务;

  • 在4.2版本中,MongoDB 引入了分布式事务,增加了对分片集群上多文档事务的支持,并合并了对副本集上多文档事务的现有支持,事务可以跨多个操作、集合、数据库、文档和分片使用,这种方式事务的实现主要是借助于两阶段提交协议(2PC)实现的。

如何使用

MongoDB 中从 4.0 开始支持了事务,这里来看看 MongoDB 中的事务是如何使用的呢?

首先登陆 MongoDB 命令行

mongo -u <name> --port 27017 --host 127.0.0.1 admin  -p <pass>

1、打开 session

session = db.getMongo().startSession( { readPreference: { mode: "primary" } } );

2、将需要操作的 collection 进行变量绑定

testCollection = session.getDatabase("gleeman").test_explain;
test1Collection = session.getDatabase("gleeman").test_explain_1;

3、开始事务标注,指定MVCC的模式,写模式

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

4、拼接执行语句,将需要执行的语句进行事务封装

try (ClientSession clientSession = client.startSession()) {
clientSession.startTransaction();
collection.insertOne(clientSession, docOne);
collection.insertOne(clientSession, docTwo);
clientSession.commitTransaction();
}

5、提交事务

session.commitTransaction();

6、关闭session

session.endSession();

事务的原理

MongoDB 中的 WiredTiger 存储引擎是目前使用最广泛的,这里主要介绍下 WiredTiger 中事务的实现原理。

WiredTiger 存储引擎支持 read-uncommitted 、read-committedsnapshot 3 种事务隔离级别,MongoDB 启动时默认选择 snapshot 隔离。

事务和复复制集以及存储引擎之间的关系

1、事务和复制集

复制集配置下,MongoDB 整个事务在提交时,会记录一条 oplog,包含了事务所有的操作,备节点拉取 oplog,并在本地重放事务操作。事务 oplog 包含了事务操作的 lsid,txnNumber,以及事务内所有的操作日志( applyOps 字段)。

WiredTiger 是如何实现事务和 ACID 呢。WiredTiger 事务主要使用了三个技术 snapshot(事务快照)、MVCC (多版本并发控制)和 redo log(重做日志)。同时为了实现这三个技术,还定义了一个基于这三个技术的事务对象和全局事务管理器。

wt_transaction{
transaction_id: // 本次事务的全局唯一的ID,用于标示事务修改数据的版本号
snapshot_object: // 当前事务开始或者操作时刻其他正在执行且并未提交的事务集合,用于事务隔离
operation_array: // 本次事务中已执行的操作列表,用于事务回滚。
redo_log_buf: // 操作日志缓冲区。用于事务提交后的持久化
State: // 事务当前状态
}

WiredTiger 中的 MVCC 是基于 key/value 中 value 值的链表,每个链表单元中存储有当先版本操作的事务 ID 和操作修改后的值。

wt_mvcc{
transaction_id: // 本次修改事务的ID
value: // 本次修改后的值
}

WiredTiger 中数据修改都是在这个链表中进行 append 操作,每次对值的修改都是 append 到链表头,每次读取值的时候读是从链表头根据对应的修改事务 transaction_id 和本次事务的 snapshot 来判断是否可读,如果不可读,向链表尾方向移动,直到找到都事务可以读到的数据版本。

什么是 snapshot 呢?

事务开始或者结束操作之前都会对整个 WiredTiger 引擎内部正在执行的或者将要执行的事务进行一次快照,保存当时整个引擎的事务状态,确定那些事务是对自己可见的,哪些事务是自己不可见的。

WiredTiger 中的事务隔离级别

传统的事务级别都分成下面四种:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。

  • 读未提交:一个事务还没提交时,它的变更就能被别的事务看到,读取未提交的数据也叫做脏读;

  • 读提交:一个事务提交之后,它的变更才能被其他的事务看到;

  • 可重复读:一个事务执行的过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的,在此隔离级别下,未提交的变更对其它事务也是不可见的,此隔离级别基本上避免了幻读;

  • 串行化:这是事务的最高级别,顾名思义就是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

我们熟知的 MySQL 对于事务隔离级别的实现,可重复读读提交 主要是通过 MVCC 来实现,MVCC 的实现主要用到了 undo log 日志版本链和 Read View。串行化 和 读未提交,主要实现方式是通过加锁来实现的。

其中 Read View 可以在理解为一个数据的快照,可重复读隔离级别会在每次启动的事务的时候生成一个 Read View 记录下当前事务启动瞬间,当前所有活跃的事务 ID。具体细节可参见 MySQL中的事务的隔离级别

WiredTiger 存储引擎支持 read-uncommitted、read-committedsnapshot 3种事务隔离级别,MongoDB 启动时默认选择 snapshot 隔离。

  • Read-Uncommited:读未提交,一个事务还没提交时,它的变更就能被别的事务看到,读取未提交的数据也叫做脏读,WiredTiger 引擎在实现这个隔方式时,就是将事务对象中的 snap_object.snap_array 置为空即可,那么在读取 MVCC list 中的版本值时,总是读取到 MVCC list 链表头上的第一个版本数据,这样就总是能读取到最新的数据了;

  • read-committed:一个事务提交之后,它的变更才能被其他的事务看到;这种对于一个长事务可能存在多次读取,读取到的值不一样,因为每次读取都是读取的最新提交的数据,WiredTiger 引擎在实现该事务隔离级别,就是在事务在每次执行之前,都对系统机型一次快照,然后在这个事务快照中读取最新提交的数据;

  • snapshot:快照隔离方式,一个事务开始时,就进行一次快照,并且只会进行一次快照,这样事务看到的值提交版本,这个值在整个事务过程中看到的都是一样;

WiredTiger 中对于事务的实现也是基于 MVCC 实现的,MVCC 可以提供基于某个时间点的快照,有了这个快照,就能确定当前事务能看到的数据了,通过这个来实现对应的事务隔离级别,这点也个人感觉和 mysql 中的 Read View 类似,不展开分析了。

WiredTiger 没有使用传统的事务独占锁和共享访问锁来保证事务隔离,而是通过对系统中写事务的 snapshot 截屏来实现。这样做的目的是在保证事务隔离的情况下又能提高系统事务并发的能力。

WiredTiger 事务过程

一般事务有三个阶段:开启事务,执行事务,提交事务。如果事务执行失败,会进行事务的回滚操作,事务正常执行,最近进行事务的提交 (commit) 即可。

事务开启

事务开启的过程中,首先会为事务创建一个事务对象并把这个对象加入到全局的事务管理器当中,然后根据配置确定事务的隔离级别和 redo_log 的刷盘方式,并将事务状态设置成执行状态,最后判断事务的隔离级别,如果是 snapshot 级的事务隔离,在本次事务执行之前会创建一个系统并发事务的 snapshot 截屏,,保存当时整个引擎的事务状态,确定那些事务是对自己可见的,哪些事务是自己不可见的。

事务执行

事务在执行阶段,如果是读操作,不做任何处理,因为读操作不需要回滚和提交。如果是写操作,WiredTiger 会对每个操作做详细的记录。

这里就会用使用到上面介绍的事务对象(wt_transaction)中的 operation_array 和 redo_log_buf。

operation_array:主要记录本次事务中已经提交的操作列表,数组单元中,会包含一个指向 MVCC list 对应修改版本值的指针,用于事务的回滚。

redo_log_buf: 操作日志缓冲区。用于事务提交后的持久化。

来描述下具体的更新操作过程:

1、创建一个 mvcclist 的值对象 update;

2、根据事务对象的 transaction_id 和事务状态判断是为本次事务创建写的事务id,如果没有,为本次事务分配一个事务id,并将事务的状态设置成 HAS_TXN_ID 状态;

3、将本次事务的 ID 设置到 update 单元中作为 mvcc 版本号;

4、同时会创建一个 operation 对象,这个对象的指针会指向 update,这个对象会加入到 operation_array 中,用来进行操作事务的回滚;

5、update 会被加入到 mvcclist 的头部;

6、最后会写一条 redo_log 到本次事务的 redo_log_buffer 当中。

事务提交

事务提交

提交事务对象中的 redo_log_buf 中的数据到 redo_log_file(重做日志中),并将 redo_log_file 持久化到磁盘上,清除提交事务对象的 snapshot,再将事务对象的transaction_id 设置成 WT_TNX_NODE,保证其他事务在创建 snapshot 时本次事务的状态是已提交的状态。

事务回滚

WiredTiger 引擎对事务的回滚过程比较简单,首先遍历 operation_array ,对每个数组单元对应的 update 事务 id 设置一个 WT_TXN_ABORTED ,标识 mvcc 对应的事务单元被回滚,在其它事务进行 mvcc 读操作的时候,跳过这个放弃的值即可,整个过程是一个无锁的操作,高效,简洁。

事务日志(journal)

Journal 是一种 WAL(Write Ahead Log)事务日志,目的是实现事务提交层面的数据持久化。

Journal 是 MongoDB 存储引擎层面的概念,MongoDB 主要支持的 mmapv1、wiredtiger、mongorocks 等存储引擎,都⽀持配置 JournalMongoDB 可以基于 Journal 来恢复因为崩溃未及时写到磁盘的信息。

Journal 持久化的对象不是修改的数据,而是修改的动作,以日志形式先保存到事务日志缓存中,再根据相应的配置按一定的周期,将缓存中的日志数据写入日志文件中。

事务日志落盘的规则如下。

  • 1、按时间周期落盘。

在默认情况下,以50毫秒为周期,将内存中的事务日志同步到磁盘中的日志文件。

  • 2、提交写操作时强制同步落盘。

当设置写操作的写关注为j:true时,强制将此写操作的事务日志同步到磁盘中的日志文件。

  • 3、事务日志文件的大小达到100MB。

总结

1、在4.0版本中,MongoDB支持副本集上的多文档事务;

2、在4.2版本中,MongoDB 引入了分布式事务,增加了对分片集群上多文档事务的支持,并合并了对副本集上多文档事务的现有支持,事务可以跨多个操作、集合、数据库、文档和分片使用;

3、MongoDB 中的 WiredTiger 存储引擎是目前使用最广泛的,WiredTiger 存储引擎支持 read-uncommitted 、read-committed 和 snapshot 3 种事务隔离级别,MongoDB 启动时默认选择 snapshot 隔离;

  • Read-Uncommited:读未提交,一个事务还没提交时,它的变更就能被别的事务看到,读取未提交的数据也叫做脏读,WiredTiger 引擎在实现这个隔方式时,就是将事务对象中的 snap_object.snap_array 置为空即可,那么在读取 MVCC list 中的版本值时,总是读取到 MVCC list 链表头上的第一个版本数据,这样就总是能读取到最新的数据了;

  • read-committed:一个事务提交之后,它的变更才能被其他的事务看到;这种对于一个长事务可能存在多次读取,读取到的值不一样,因为每次读取都是读取的最新提交的数据,WiredTiger 引擎在实现该事务隔离级别,就是在事务在每次执行之前,都对系统机型一次快照,然后在这个事务快照中读取最新提交的数据;

  • snapshot:快照隔离方式,一个事务开始时,就进行一次快照,并且只会进行一次快照,这样事务看到的值提交版本,这个值在整个事务过程中看到的都是一样;

4、WiredTiger 中对于事务的实现也是基于 MVCC 实现的,MVCC 可以提供基于某个时间点的快照,有了这个快照,就能确定当前事务能看到的数据了,通过这个来实现对应的事务隔离级别,这点也个人感觉和 mysql 中的 Read View 类似,不展开分析了;

5、WiredTiger 没有使用传统的事务独占锁和共享访问锁来保证事务隔离,而是通过对系统中写事务的 snapshot 截屏来实现。这样做的目的是在保证事务隔离的情况下又能提高系统事务并发的能力。

参考

【MongoDB事务】https://docs.mongoing.com/transactions

【WiredTiger的事务实现详解 】https://blog.csdn.net/daaikuaichuan/article/details/97893552

【MongoDB中并发控制】https://blog.csdn.net/baijiwei/article/details/89436861

MongoDB 中的事务的更多相关文章

  1. 聊聊MongoDB中连接池、索引、事务

    大家好,我是哪吒. 三分钟你将学会: MongoDB连接池的使用方式与常用参数 查询五步走,能活九十九? MongoDB索引与MySQL索引有何异同? MongoDB事务与ACID 什么是聚合框架? ...

  2. MongoDB中的读写锁

    1. MongoDB 使用的锁 MongoDB 使用的是“readers-writer”锁, 可以支持并发但有很大的局限性当一个读锁存在,许多读操作可以使用这把锁,然而, 当一个写锁的存在,一个单一的 ...

  3. MongoDB中的一些坑( 2.4.10 版本)

    http://www.jb51.net/article/62654.htm 1.MongoDB 数据库级锁 MongoDB的锁机制和一般关系数据库如 MySQL(InnoDB), Oracle 有很大 ...

  4. MongoDB: 原子性和事务

    在MongoDB中, 文档级别的的写操作是原子性的, 甚至是在对某个文档的操作中修改其多个内嵌的子文档, 也是原子性的. 在一个写操作同时修改多个文档的情况, 对其中单独的某个文档而言是原子的, 但是 ...

  5. 在 MongoDB 上模拟事务操作来实现支付

    我们的产品叫「学海密探」,属于在线教育行业,产品需要有支付功能,然而支付最蛋疼是什么?有人会说是支付宝和微信等支付接口的接入开发!没错,但支付接口的开发算是比较简单的了,我觉得凡是跟钱有关系的操作最重 ...

  6. MongoDB中的一些坑(最好不要用)

    MongoDB 是目前炙手可热的 NoSQL 文档型数据库,它提供的一些特性很棒:如自动 failover 机制,自动 sharding,无模式 schemaless,大部分情况下性能也很棒.但是薄荷 ...

  7. MongoDB中WiredTiger的数据可用性设置

    此文已由作者温正湖授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. MongoDB中WiredTiger的参数配置主要通过 wiredtiger_open (http://so ...

  8. Java生鲜电商平台-SpringCloud微服务架构中分布式事务解决方案

    Java生鲜电商平台-SpringCloud微服务架构中分布式事务解决方案 说明:Java生鲜电商平台中由于采用了微服务架构进行业务的处理,买家,卖家,配送,销售,供应商等进行服务化,但是不可避免存在 ...

  9. MongoDB 4.0 事务实现解析

    MongoDB 4.0 引入的事务功能,支持多文档ACID特性,例如使用 mongo shell 进行事务操作 > s = db.getMongo().startSession() sessio ...

  10. MongoDB中如何优雅地删除大量数据

    删除大量数据,无论是在哪种数据库中,都是一个普遍性的需求.除了正常的业务需求,我们需要通过这种方式来为数据库"瘦身". 为什么要"瘦身"呢? 表的数据量到达一定 ...

随机推荐

  1. wget: 未找到命令

    输入以下命令: yum -y install wget

  2. C#.NET 国密SM2 签名验签 与JAVA互通 ver:20230807

    C#.NET 国密SM2 签名验签 与JAVA互通 ver:20230807 .NET 环境:.NET6 控制台程序(.net core). JAVA 环境:JAVA8(JDK8,JAVA 1.8), ...

  3. LeetCode 周赛上分之旅 # 37 多源 BFS 与连通性问题

    ️ 本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 和 BaguTree Pro 知识星球提问. 学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越 ...

  4. .NET Core多线程 (4) 锁机制

    合集:.NET Core多线程温故知新 .NET Core多线程(1)Thread与Task .NET Core多线程(2)异步 - 上 .NET Core多线程(3)异步 - 下 .NET Core ...

  5. 《Java极简设计模式》第01章:单例模式(Singleton)

    作者:冰河 星球:http://m6z.cn/6aeFbs 博客:https://binghe.gitcode.host 文章汇总:https://binghe.gitcode.host/md/all ...

  6. 知识图谱(Knowledge Graph)- Neo4j 5.10.0 使用 - Python 操作

    数据基于: 知识图谱(Knowledge Graph)- Neo4j 5.10.0 使用 - CQL - 太极拳传承谱系表 这是一个非常简单的web应用程序,它使用我们的Movie图形数据集来提供列表 ...

  7. 如何修改min.js或者压缩后的js,以便提高代码的可读性。

    前端的js上线的时候一般会使用打包工具处理(webpack,gulp,ugly.js 等).这样做有几点作用. 可以压缩空间,提高页面响应速度 一定程度上可以保护自己的代码安全,防止别人清晰看懂逻辑或 ...

  8. 交换机通过SFTP进行文件操作

    组网图形  通过SFTP进行文件操作简介 配置设备作为SFTP服务器,用户可以在终端通过SFTP通信方式,利用SSH协议提供的安全通道与远端设备进行安全连接.通过SFTP进行文件操作的方式对数据进行了 ...

  9. 【matplotlib基础】--刻度

    Matplotlib中刻度是用于在绘图中表示数据大小的工具. 刻度是坐标轴上的数字或标签,用于指示数据的大小或值,通常以整数或小数表示,具体取决于坐标轴的类型和限制. 1. 主次刻度 默认的绘制时,坐 ...

  10. 请大家一定不要像我们公司这样打印log日志

    前言 最近接手了公司另一个项目,熟悉业务和代码苦不堪言. 我接手一个新项目,有个习惯,就是看结构,看数据库,搜代码. 其中搜代码是我个人这些年不知不觉形成的癖好,我下面给大家展示下这个小癖好. 正文 ...