InnoDB源码解析-事务系统
InnoDB事务系统负责InnoDB层事务管理,快照管理,MVCC等。
事务对象
InnoDB层事务对象为 struct trx_t 类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// 部分字段 struct trx_t { trx_id_t id; // 事务开启序 trx_id_t no; // 事务提交序 trx_state_t state; // 事务状态 ReadView *read_view; // 快照 trx_lock_t lock; // 事务持有的锁 lsn_t commit_lsn; // commit写redo时的lsn THD *mysql_thd; // 对应的THD UT_LIST_NODE_T(trx_t) mysql_trx_list; // 所有事务链表节点对象 undo_no_t undo_no; bool read_only; XID *xid; // XA XID ... }; |
InnoDB的事务开启序,每个读写事务启动时获取一个id,事务结束时获取no作为提交序。
事务状态包含:
- TRX_STATE_NOT_STARTED
- TRX_STATE_FORCED_ROLLBACK
- TRX_STATE_ACTIVE
- TRX_STATE_PREPARED
- TRX_STATE_COMMITTED_IN_MEMORY(COMMITTED)
常规的事务状态变换:NOT_STARTED -> ACTIVE -> COMMITTED -> NOT_STARTED。
TRX_STATE_PREPARED是针对XA事务的prepare状态。
所有的事务都会存放在mysql_trx_list链表中,这是一个双向链表,本身不支持并发,需要trx_sys->mutex保护。
read_view是该事务的快照,我们后面会详细描述这块。
commit_lsn跟redo提交有关,我们在InnoDB log sys中介绍。
事务对象管理
事务对象管理分为两块,一块是活跃的读写事务集合,一块是所有活跃事务集合(包括读写事务和只读事务)。
所有活跃事务集合以链表形式存储,也就是mysql_trx_list。
活跃的读写事务集合非常重要,存放在trx_sys->rw_trx_ids,它跟快照关系密切,实际上也只是为了快照才维护了这个数据结构。
快照
如前所说,InnoDB是开启序,写事务开启后申请到一个id,事务修改了某一行,会在行头写入对应的事务id,以后该行可能被其他并发事务读取,至于能不能看到该行的这个新版本,取决于读事务的快照是否认为写事务已经提交。可能存在id小的写事务一直没提交,但id大的事务先提交了,所以对快照来说,创建快照时刻的活跃事务列表就成了数据版本可见性的依据,也就是快照的一部分。
快照的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
struct ReadView { // 超过该值的tid对应事务在本快照中认为还没开始 trx_id_t m_low_limit_id; // 小于该值的tid对应事务在本快照中认为已经结束 trx_id_t m_up_limit_id; // 小于该值的tid对应事务在本快照中认为已经结束 // 当前事务自己,事务可以看到自己写的数据 trx_id_t m_creator_trx_id; // 介于m_up_limit_id和m_low_limit_id之间的事务, // 如果在m_ids中,则本快照认为该事务是活跃,否则认为是已经提交 ids_t m_ids; // 本快照能看到的最老的提交序,低于该值的版本本快照不再需要 trx_id_t m_low_limit_no; // 将所有快照串成链的node对象 UT_LIST_NODE_T(ReadView) m_view_list; }; |
申请一个快照的过程:
- 获取一个view对象,可以从free view list拿一个复用,或者分配一个新的
- 将活跃读写事务列表复制到view->m_ids
- m_low_limit_id为当前已经分配出去的最大trx id
- m_low_limit_no为已经分配了提交序no的事务中最小的(遍历serialisation_list列表),如果列表为空,则与m_low_limit_id相等。
- m_up_limit_id为view->m_ids中最小值,如果view->m_ids为空,m_up_limit_id也等于m_low_limit_id
上述过程在trx_sys->mutex保护下进行。
快照对象的管理
所有已经open的快照都在 MVCC::m_views列表中保存,该列表也由trx_sys->mutex保护。
为了避免反复分配释放快照对象,如果快照对象close时,先还到MVCC::m_free这个free list中。
purge等地方需要用到当前系统中最老的快照。获取最老快照实现方式 MVCC::get_oldest_view 是在MVCC::m_views中找第一个已经open的快照,这个过程也是在trx_sys->mutex保护进行。
之所以可以直接取列表第一个open的read view,是因为在trx_sys->mutex锁的同步下,快照的创建完全是串行化的,时间上越早创建的快照越在MVCC::m_views前面。