MongoDB的事务实现

在前面我们介绍了MongoDB单文档原子操作, 这是单文档下的事务, 它能够保证对於单文档的操作的原子性。 在MongoDB3.6里面, 支持了session, 这与之前的OperationContext很类似, 所不同的实, session可以多个CRUD操作使用共同的session。在MongoDB4.0里面, 终于有了针对副本集的多文档事务实现, 使得我们可以向使用其他的数据库事务一样进行事务操作。
WiredTiger对事务的支持, 是MongoDB支持事务的基础, 这里重点介绍一下WT下事务的实现过程。

transaction 相关的数据结构

__wt_txn_global

在WT_CONNECTION中, 有一个全局的事务相关的上下文结构体__wt_txn_global, 用来记录事务实现的全局信息。 在open connection的时候调用__wt_txn_global_init来构建和初始化, 在关闭connection的时候调用__wt_txn_global_destroy,来清理资源。 主要包括:

  • 全局唯一的自增事务ID;
  • 最旧的未提交事务ID等;
  • checkpoint 相关信息;
  • named snapshot队列信息;
  • 各个session对应state的数组;

为了支持事务, 在__wt_txn_global里面增加了如下的field:

  • commit_timestamp
  • last_ckpt_timestamp
  • meta_ckpt_timestamp
  • oldest_timestamp
    oldest_timestamp记录了一个时间点, 在这个时间点之前的版本不需要保存, 由于每个读操作都与一个snapshot关联, 只要有读操作与之相应, 该版本就需要保留, 为了不保留太多的版本, 给WT的内存造成压力, oldest_timestamp就是告诉引擎层, 该时间点之前的版本不需要保留。
  • pinned_timestamp
  • recovery_timestamp
  • stable_timestamp
    stable_timestamp记录了当前已经同步到大多数节点的操作的时间点,随着新的操作的进行, 该时间点逐渐变大, 当我们进行回滚操作, 不再像之前那样, 对要回顾的操作进行相反的逆操作, 而是直接回到stable_timestamp。
  • TAILQ_HEAD(__wt_txn_cts_qh, __wt_txn) commit_timestamph;
  • TAILQ_HEAD(__wt_txn_rts_qh, __wt_txn) read_timestamph;
类型 名称 描述
uint64_t current 全局唯一的递增transactionID
uint64_t last_running 最旧的运行中的transaction id
uint64_t oldest_id 系统中最小的transaction id
wt_timestamp_t commit_timestamp 记录事务可以读取的时间点, 再次之前是读不到的
wt_timestamp_t last_ckpt_timestamp 上一次checkpoint 的时间
wt_timestamp_t meta_ckpt_timestamp metadata里面记录的checkpoint time
wt_timestamp_t oldest_timestamp 记录最旧的事务时间点
wt_timestamp_t pinned_timestamp 当前最小的需要固定在内存的transactionID, 就是有read绑定到它上面
wt_timestamp_t recovery_timestamp 记录recovery的时候, 恢复到的checkpoint的timestamp
wt_timestamp_t stable_timestamp 一个固定时间点, 由客户端设定, 主要用于rollback_to_stable
bool has_commit_timestamp 是否有commit_timestamp
bool has_oldest_timestamp 是否有oldest_timestamp
bool has_pinned_timestamp 是否有pinned_timestamp设定
bool has_stable_timestamp 是否有set_timestamp设定stable
bool oldest_is_pinned pinned_timestamp == oldest_timestamp?
bool stable_is_pinned pinned_timestamp == stable_timestamp?
WT_SPINLOCK id_lock 用于更新current, 获取一个transactionID的时候用
WT_RWLOCK visibility_rwlock 可见性保护锁, 防止日志, checkpoint,事务完成之前可见
WT_RWLOCK commit_timestamp_rwlock commit_timestamp读写锁, 修改commit timestamp队列的时候用
TAILQ_HEAD(__wt_txn_cts_qh, __wt_txn) commit_timestamph commit_timestamp队列
uint32_t commit_timestampq_len commit_timestamp的长度
WT_RWLOCK read_timestamp_rwlock read_timestamp读写锁
TAILQ_HEAD(__wt_txn_rts_qh, __wt_txn) read_timestamph read_timestamp队列
uint32_t read_timestampq_len read_timestamp队列的长度
bool checkpoint_running checkpoint是否正在运行中
uint32_t checkpoint_id checkpoint id
WT_TXN_STATE checkpoint_state checkpoint的状态
uint64_t metadata_pinned 绑定的metadata中最小的transactionID
WT_RWLOCK nsnap_rwlock 命名snapshot的读写锁
uint64_t nsnap_oldest_id 最旧的命名snapshot ID
TAILQ_HEAD(__wt_nsnap_qh, __wt_named_snapshot) nsnaph 命名snapshot队列
WT_TXN_STATE *states 事务列表的状态

__wt_txn

每一个session, 都有一个相关的上下文状态的结构__wt_txn, 它用来产生事务结构体, 并且记录了session的当前正在执行的transaction的相关信息。主要包括:

  • 事务的ID;
  • snapshot 数组;
  • WT_TXN_OP 修改数组;
  • log record日志记录;
  • checkpoint的状态信息;
  • transaction的类型状态信息;
  • commit_timestamp
  • durable_timestamp
  • first_commit_timestamp
  • prepare_timestamp
  • read_timestamp
  • TAILQ_HEAD(__wt_txn_cts_qh, __wt_txn) commit_timestamph;
  • TAILQ_HEAD(__wt_txn_rts_qh, __wt_txn) read_timestamph;
类型 名称 描述
uint64_t id transaction ID
WT_TXN_ISOLATION isolation 事务的隔离级别: read uncommitted, read committed, snapshot
uint32_t forced_iso 强制使用的隔离级别
uint64_t snap_min, snap_max 该事务对应的snapshot的最大最小id
uint64_t * snapshot 该事务对应的snapshot数组
uint32_t snapshot_count 该事务的对应的snapshot数组的大小
uint32_t txn_logsync 事务日志的sync设定
wt_timestamp_t commit_timestamp 该transaction的commit timestamp
wt_timestamp_t first_commit_timestamp commit_timstamp在一个transaction可以被设定多次, 这里记录第一个
wt_timestamp_t prepare_timestamp 该事务的prepare timestamp
TAILQ_ENTRY(__wt_txn) commit_timestampq commit timestamp队列
TAILQ_ENTRY(__wt_txn) read_timestampq readtimestamp队列
bool clear_commit_q commit timestamp队列是否需要被清理
bool clear_read_q read timestamp队列是否需要被清理
WT_TXN_OP *mod transaction里面的修改操作数组
size_t mod_alloc transaction里面的修改操作数组大小
u_int mod_count transaction里面的修改操作数组已分配的个数
WT_ITEM* logrec transaction对应的log内存buffer, 记录了transaction的操作
WT_TXN_NOTIFY * notify transaction的notify函数
WT_LSN ckpt_lsn checkpoint的logical sequence number
uint32_t ckpt_nsnapshot checkpoint对应的snapshot count
WT_ITEM* ckpt_snapshot 记录checkpoint的snapshot数组的ID
bool full_ckpt 是不是fullcheckpoint
const char * rollback_reason transaction rollback的原因
uint32_t flags transaction的设定

__wt_txn_state

Per-session的transaction状态信息。

事务的ACID

  • 原子性(Atomicity):事务内的所有操作要么全部完成,要么全部回滚;
  • 一致性(Consistency):事务执行前后的状态都是合法的数据状态,不会违反任何的数据完整性;
  • 隔离性(Isolation):主要是事务之间的相互的影响,根据隔离有不同的影响效果。
  • 持久性(Durability):事务一旦提交,就会体现在数据库上,不能回滚

这些基本的特性是如何保证的哪?对比mysql, 参考下面的表格:

事务属性 MySQL MongoDB
Atomicity undo log机制来实现 journal 机制来实现
Consistency redo log机制来实现 journal机制来实现
Isolation 隔离级别+ 读写锁 + MVCC 来实现 隔离级别 + 读写锁 + snapshot+ MVCC来实现
Durability 依赖WAL 刷盘频率的设定 依赖WAL 刷盘频率的设定

事务的隔离级别

log刷盘设

4.0事务的限制

  • 事务执行时间的限制
    默认情况下, 一个事务从开始到事务提交的时间间隔要小于60秒, 如果大于60秒, 该事务就认为失败, 进行回滚。这个时间限制可以通过transactionLifetimeLimitSeconds来进行修改。

    db.adminCommand( { setParameter: 1, transactionLifetimeLimitSeconds: 40 } )
    
  • oplog size的限制
    在MongoDB下面, 默认一个JSON文件的最大为16M, oplog本身也是一个JSON,也遵守这个规则。一个事务, 包括它里面所有的写操作都包括在一个oplog里面。

  • 事务获取锁的等待时间限制
    默认情况下, 使用事务需要获取锁的等待时间为5毫秒, 超过这个时间事务就失败。我们可以通过修改参数maxTransactionLockRequestTimeoutMillis开改变这个设定。

    db.adminCommand( { setParameter: 1, maxTransactionLockRequestTimeoutMillis: 20 } )
    
  • 事务写冲突的限制

时间点 事务1 事务2
t1 x=1
t2 begin_transaction
t3 x=2
t4 y=1
t5 y=2
t6 commit

如上图所示, 有事务1和事务2, you个时间t1 ~ t5.在时间点t1, 事务2将X设定为1, 在t3, 另外一个事务将其该为2, 由于事务2没有在一个局部的事务里面, 它的操作可以认为在事务之外, 这种情况下, 事务1会失败;
另外一种情况, 在事务1内部时间点t4,y的值被改为1, 在事务外部t5, y的值被改为2, 这种情况下, y=2的设定要等待事务1提交之后, 才会被处理;

  • 事务读取旧的数据
时间点 事务1 事务2 事务3
t1 x=1 y=1
t2 begin_transaction
t3 begin_transaction y=2
t4 x=2 y=3
t5 commit x=3
t6 commit

如上图所示, 在t1是x=1,y=1,事务1 在时间点t4将x变成2, 事务2在时间点t3将y值设定为2,在外部的在t4将y设定为3, 但是在整个的事务2(t2 ~ t5), 我们读取的事务2的y的值始终是2.

事务的执行过程

整体上来看, MongoDB的事务的使用和其他的数据库非常类似, 如下是从官网复制的一个transaction API的例子, 我们顺着程序的执行顺序来分析下事务的实现。

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

employeesCollection = session.getDatabase("hr").employees;
eventsCollection = session.getDatabase("reporting").events;

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

// Operations inside the transaction
try {
   employeesCollection.updateOne( { employee: 3 }, { $set: { status: "Inactive" } } );
   eventsCollection.insertOne( { employee: 3, status: { new: "Inactive", old: "Active" } } );
} catch (error) {
   // Abort transaction on error
   session.abortTransaction();
   throw error;
}

// Commit the transaction using write concern set at transaction start
session.commitTransaction();
session.endSession();

startSession

startSession 是3.6版本新开放的API,它可以产生一个内部的session, 它联合client的write concern 和read concern, 可以实现因果一致性

startTransaction

对于某个session, 在任一时刻只能有一个运行的transaction。
通过__session_begin_transaction --> __wt_txn_begin调用__wt_txn_get_snapshot, 来产生一个新的事务。其实, 产生一个snapshot的过程很简单, 就是在__wt_txn_global->stats, 将某个session->id指定为当前的__wt_txn_state, 更新里面的字段, 当我们需要某个transaction的时候, 通过TXN的ID可以找到相关的snapshot的ID。

transaction中间的CRUD

在startTransaction之后, commitTranscation/abortTransaction之前的操作, 都是在同一个事务里面, 遵守事务的ACID特性。
相应的操作除了直接应用到内存的数据和索引内存页之外, 还记录到journal对应的txn->mod里面, 这样, 如果事务失败, 通过__wt_txn_unmodify, 回滚log里面记录的事务内的操作, 可以实现事务的回滚。
在内存里面, 内存页里面有page对应的insert_array和update_array;在一个事务里面, 每一个更新操作, 都有一个update操作对应__wt_txn_modify, 将该操作与实务id相关联。

commitTransaction

__wt_txn_commit
当事务需要提交的时候, __wt_txn_commit需要根据配置设定事务的日志提交方式, 然后通过__wt_txn_log_commit写入日志。 如果写入成功, 就释放掉该事务对应的修改操作txn->mod, 并且将事务清除; 如果失败, 就调用__wt_txn_rollback rollback处理。

abortTransaction

一旦需要rollback, __wt_txn_rollback会将txn->mod的所有需改, 状态变成WT_TXN_ABORTED, 然后清理事务对应的资源。

参考文档

https://source.wiredtiger.com/3.0.0/transactions.html
https://www.jianshu.com/p/9b83ea78b380
https://www.cnblogs.com/cjsblog/p/8365921.html
https://baijiahao.baidu.com/s?id=1625607423998953705&wfr=spider&for=pc

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章