前言
WiredTiger存储引擎系列文章将从逻辑正确、内容完整的角度全面介绍WiredTiger存储引擎。本篇是WiredTiger存储引擎系列文章第5篇,前面篇章分别是:
WiredTiger存储引擎之一:基础数据结构分析
WiredTiger存储引擎之二:一个Page的生命周期
WiredTiger存储引擎之三:Checkpoint原理
WiredTiger存储引擎之四:WT工具编译与元数据文件剖析
本篇包含以下内容:
- 与事务相关的数据结构是如何支撑事务的?
- MongoDB事务采取的多版本并发控制机制(MVCC)
事务的数据结构
事务在内存里面也会维护相应的数据结构以支撑事务的并发、回滚、持久化等操作,事务相关的数据结构如下图所示:
图:事务相关的数据结构
上图左边是一个leaf page在内存的数据结构,放在这的目的是为了更好的看到内存里的修改操作与事务的关系,本节重点关注事务的数据结构即WT_TXN,详细描述如下:
id字段
这是事务的全局唯一标识,通过它与具体的操作关联,这样就能知道一个事务里面包含哪些操作。
snapshot_data字段
因为MongoDB使用的是快照隔离级别的事务,这个字段保存事务的快照信息,具体来说它会有snap_min和snap_max两个属性,通过这两个属性能够计算一个事务开始时能够看到的数据范围,每个事务开始时都会构造一个这样的快照。
commit_timestamp字段
表示事务提交的时间。
durable_timestamp字段
表示事务修改的数据已持久化的时间,与具体操作里面的durable_ts字段关联。
prepare_timestamp字段
表示事务开始准备的时间。
WT_TXN_OP字段
包含事务的修改操作,用于事务rollback和生成事务的Journal日志。
logrec字段
表示事务日志的缓存,用于在内存里面保存事务日志(对于MongoDB来Journal日志就是事务日志)。
MVCC并发控制机制
要实现事务间的并发操作,可以使用锁机制或MVCC控制等。
对于WiredTiger来说,使用的是MVCC控制来实现并发的,相较于其它锁机制的并发,MVCC实现的是一种乐观并发机制,因此它较轻量级。
MVCC是通过在内存里面维护一个多版本的行数据(leaf page上的WT_UPDATE结构),也就是说它会将多个写操作,针对同一行记录的修改以不同行版本号的形式保存下来,实现事务的并发。
具体如何实现事务并发与冲突检查,如下图所示:
图:MVCC并发控制机制
详细描述如下:
1) A事务首先从表中读取到要修改的行数据,读取到库存值为100,行记录的版本号为1;
2) B事务也从中读取到要修改的相同行数据,读取库存值为100,行记录版本号为1;
3) A事务修改库存值后提交,同时行记录版本号加1,即变为2,大于一开始读取到的版本号1,A事务可以提交;
4) 但B事务提交时发现此时行记录版本号已经变为2,产生冲突,B事务提交失败;
5) B事务尝试重新提交,此时再读取到的版本号为2,加1,即变为3,不会产生冲突,正常提交B事务。
下面再通过一段代码来分析事务的并发与冲突:
代码语言:javascript复制session1 = client.start_session()#开启一个session1
session1.start_transaction()#开启一个事务1
inventory.delete_one({'_id':4}, session=session1)
doc1 = inventory.find_one({'_id': 4},session=session1)
pprint.pprint(doc1)#会输出none,说明在事务里面已经删除
session2 = client.start_session()#再开启一个session2
session2.start_transaction()#在session2中开启一个事务
inventory.delete_one({_id:4}, session=session2)#执行会发生事务冲突
session1.abort_transaction()#终止事务1
session1.end_session()#结束session1
session2.abort_transaction()#终止事务2
session2.end_session()#结束session2
doc2 = inventory.find_one({'_id': 4})#隐式开启第三个session和事务
pprint.pprint(doc2)#在事务外可以找到,说明事务1被终止后,回滚了
其中语句:doc1 = inventory.find_one({'_id': 4},session=session1),在session1内部查询,数据已被删除,访问不到;
语句:inventory.delete_one({‘_id’:4}, session=session2),两个未提交的事务(session1和session2中的删除操作),同时修改相同的文档,则产生冲突。
专栏作者:郭远威