MVCC 简介
在MySQL 5.6 中,默认的隔离级别是 REPEATABLE READ[1],这个级别的隔离可以避免脏读以及不可重复读的问题,但是无法避免幻影读问题。隔离级别越高,事务的并发就会越弱,因此主流的数据库一般选择REPEATABLE READ 或者 READ COMMITTED 作为默认隔离级别。由于默认的隔离级别仍然可能会出现事务并发问题,因此就需要控制并发事务的读写操作。
事务并发控制机制可以分为两大类,一类是Two-Phase Locking(2PL),另一类是Timestamp Ordering(T/O)。采用2PL,事务需要在访问数据前加锁。既然涉及到锁,就有可能会产生死锁。可以采用一些死锁避免、死锁检测的方法处理。最基本的T/O方法根据读写事务的时间戳解决读写冲突[4],并且还需要为涉及到的事务做数据的拷贝,以保证可重复读。从以上两种机制的描述可以看出,采用2PL的方式,在存在大量并发读写事务时,锁争用可能会成为瓶颈;而采用T/O 则可能会涉及到大量的拷贝操作,导致内存资源紧张。
MVCC 属于T/O类并发控制机制,因此会按照时间顺序保存数据的版本。随着系统内事务的运行,保存的数据量会逐渐增大,需要进行清理。T/O类并发控制清理的方式主要有两种,一种是单独运行一个GC线程,另一种是在处理事务扫描数据记录时删除旧版本记录。MySQL 的MVCC采用独立的purge线程清理旧的记录。
MySQL MVCC
MySQL 的InnoDB引擎支持MVCC[5],而其他的存储引擎,比如MyISAM 则不支持。InnoDB 的MVCC 是通过undo log来实现的[8]。
根据[6][9] 提到的记录事务read 时可见id范围的read view 结构体以及undo log 文件的组织方式 rollback segment可以大致确定MySQL 中与MVCC实现相关的代码位置。在下图所示的源码文件中可以找到很多undo log、purge 以及rollback segment相关的变量与函数。在storage/innobase/srv
目录下的四个源码文件中,srv0conc.cc
主要包含线程控制以及一些atomic 操作相关的函数;srv0mon.cc
提供对数据库表、锁相关等信息的统计,涉及到的指标可以查看 storage/innobase/include/srv0mon.h/monitor_id_t
。剩下的两个文件srv0start.cc
和 srv0srv.cc
与本文要介绍的MySQL MVCC的实现的相关性比较大。下面就分别对这两个以及storage/innobase/include/trx0sys.h
中相关的函数和变量做简单介绍。
srv0start.cc
首先看一下这个源码文件中可能和undo log 相关的函数与变量。
srv_undo_space_id_start
根据[9] 关于文件结构的描述,是undo tablespace的起始id,最小是1
SRV_UNDO_TABLESPACE_SIZE_IN_PAGES
定义tablesspace 占用页面的个数。tablespace 的总大小是固定10MB,页面的大小根据宏定义,UNIV_PAGE_SIZE_DEF
是 16KB,此外还有一个页面最小值UNIV_PAGE_SIZE_MIN
为 4KB,以及最大值UNIV_PAGE_SIZE_MAX
为 16KB。
srv_undo_tablespace_create
srv_undo_tablespace_open
srv_undo_tablespaces_init
srv_start_wait_for_purge_to_start
srv0srv.cc
srv_undo_dir
保存rollback 文件的目录路径srv_undo_tablespaces
rollback segment 使用的tablespaces 的个数,默认值是8srv_undo_tablespaces_open
被打开并可被使用的tablespaces 的个数,默认值是8srv_undo_logs
rollback segments 个数,默认值是1srv_n_purge_threads
在事务提交之后,用于purge rollback segments的线程个数,默认值是1srv_purge_batch_size
一个batch purge的page个数,默认值是20srv_do_purge
清除已提交事务的undo log pagessrv_is_undo_tablespace
根据space_id 判断是否是undo tablespace
trx0sys.h
trx_sys_t
是事务处理时比较重要的数据结构,在该结构体中包含指向rollback segments 的数组trx_rseg_t* const rseg_array[TRX_SYS_N_RSEGS]
,数组中的每个指针指向一个rollback segment。其中TRX_SYS_N_RSEGS
默认值是128。在storage/innobase/include/trx0rseg.h
中包含trx_rseg_t
结构体的定义.trx_rseg_t
定义了rollback segment 在内存中的组织方式。结构体中记录了rollback segment的 id、tablespace、占用的page编号等。在该结构体中包含四个列表,分别是update_undo_list
、update_undo_cached
、insert_undo_list
和insert_undo_cached
。这里按照操作对可见性的要求使用两种list,插入操作使用insert_undo_xxx
、而数据更新和删除则使用update_undo_xxx
。这是因为插入的记录在事务提交之前只对当前事务可见,而数据的更新与删除操作则需要维护多版本的信息[9]。
trx_undo_t
结构体的定义在源码文件storage/innobase/include/trx0undo.h
中,作为上面提到的rollback segment的一个slot。结构体的详细定义如下所示。该结构体中记录了该slot 的编号、类型、删除标记、所属事务id、与分布式事务XA Transaction [10]相关的 xid、存放该slot的space以及内存页面相关的信息等。
/** Transaction undo log memory object; this is protected by the undo_mutex
in the corresponding transaction object */
struct trx_undo_t{
/*-----------------------------*/
ulint id; /*!< undo log slot number within the
rollback segment */
ulint type; /*!< TRX_UNDO_INSERT or
TRX_UNDO_UPDATE */
ulint state; /*!< state of the corresponding undo log
segment */
ibool del_marks; /*!< relevant only in an update undo
log: this is TRUE if the transaction may
have delete marked records