MVCC
如果对undolog、隐藏列、readView不太了解的,可以先了解一下再看本文。
mysql中对于并发问题的处理有很多手段,其中对于经典场景【读-写】,mysql中innoDB引擎采用了MVCC多版本控制的手段来解决读写问题。
undolog
首先mysql中有undolog的机制,undolog就是当有写数据进行操作时,mysql会在真正执行写操作前,将现有数据备份至undolog中,而且多次的修改还会被分多次,那么对于同一条数据就会产生多条undolog,并且每条日志之间进行链表式串联。如下图所示:
隐藏列
下面隐藏字段中事务ID就标识了当前数据由哪个事务ID进行“写”的。
隐藏字段 | 含义 |
---|---|
DB_TRX_ID(Database Transaction Identifier)数据库事务标识符 | 最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID |
DB_ROLL_PTR (Database Rollback Pointer)数据库回滚指针 | 回滚指针,指向这条记录的上一个版本,用于配合 undo log,指向上一个版本 |
DB_ROW_ID(Database Row Identifier) 数据库行标识符 | 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段 |
快照读与当前读
MVCC正是利用了快照读这种特性,使得innodb引擎在做读-写时并不需要通过加锁的方式来控制并发请求,而是通过写时加行级锁,读时进行快照读(前提是读操作不主动加锁)的方式处理了并发读写问题。而下方的readView便是实现快照读的一种手段。
readView
readView可以理解为查询数据时产生的一个视图(若查询语句加锁则不会产生该视图)
readView的组成
- creator_trx_id: 创建这个readView的事务ID(只有写操作才会分配,读事务中事务ID=0)
- trx_ids: 表示生成readView的那一刻,当前的活跃事务ID集合(未提交的事务)。
- up_limit_id: 最小的活跃的事务ID。
- low_limit_id: 表示生成readView后,系统中下一个事务应该被分配的ID值。比如当前系统中最大的事务ID=9,那么low_limit_id=9+1=10,即便9可能不是活跃的事务ID.
readView的规则
我们举一个例子进行带入,来说明一下readView都有哪些判断逻辑。(先以READ COMMITED隔离级别举例)
- Read Committed: 每次读取数据前都生成一个readView
- Repeatable Read: 在事务开启后第一次查询时生成一个readView, 之后的查询都用当前的这个readView,若出现了对数据的update,会更新readView.
当前sku表中数据
id | sku_id | sku_name | version | DB_TRX_ID |
---|---|---|---|---|
1 | 1001 | 棒球帽 | 1 | 8 |
其中DB_TRX_ID
为隐藏列
现在有两个事务T1(txid=10)、T2(txid=20),T1进行写数据,T2进行读数据。
当前事务为T1(txid=10)
begin;
update sku set sku_name = '鸭舌帽' where id = 1;
update sku set sku_name = '贝雷帽' where id = 1;
...
当前事务为T2(txid=20)
begin;
update order set status = 'DONE' where id = 'xxx';
...
那么此时sku表中id=1的这条数据的版本链如下图所示:
基于上述场景,现在有个新事务执行查询操作:
begin;
select sku_name from sku where id = 1; # 此时得到的结果是棒球帽;
步骤拆解如下:
- 执行select语句时先生成readView,该readView的trx_ids=[10, 20], up_limited_id = 10, low_limited_id = 21, creator_trx_id = 0;
- 从当前上述的版本链中开始找数据,最新的数据name=‘贝雷帽’,对应trx_id=10,在trx_ids的范围内,不符合,根据指针找更小版本。
- 下一个name=‘鸭舌帽’,对应trx_id也等于10,也不符合,继续找。
- 下一个name=‘棒球帽’,对应trx_id=8,不在活跃事务ID范围内且小于up_limited_id,那么这个版本是符合要求的,于是最终的结果就是【棒球帽】;
我们一起来看下规则
- 当被访问版本对应的trx_id与readView中的creator_trx_id相等,说明当前读请求和写请求处在同一事务中,那么该版本数据可以被读取。
- 当被访问数据的trx_id < readView中的up_limit_id,说明该数据对应事务是已提交的,那么也可以被访问。
- 当被访问数据trx_id > readView中的low_limit_id,说明该版本是在当前事务生产readView之后生成的,那么该版本数据不可被读取。
- 当被访问数据的 trx_id在 up_limit_id和low_limit_id之间,那就要看trx_id是否存在于 trx_ids数组内。存在,说明该事务是活跃的事务,不可访问。 不存在,说明事务已提交,可以访问。
那么由上述规则加上例子,我们可以清晰的了解到mvcc是如何通过readView+undolog+隐藏列处理事务的并发读写问题的。
下一篇:
对于RR和RC如何选择?是否可以直接使用RC?