前言
本文主要介绍InnoDB高并发性能关键技术,MVCC(Muti-Version Concurrency Control)多版本并发控制,意思是基于多版本的并发控制协议,通过版本号机制,避免同一数据在不同事务的锁竞争,而提高读并发。
环境:
操作系统:macos
数据库版本:mysql 8.0.23 innodb引擎
本文所有内容都是基于以上环境的,如果有异议的地方欢迎邮件交流。
核心概念
隐藏列
mysql会为每一行记录生成隐藏列,如下:
DB_TRX_ID
:事务ID,是根据事务产生时间顺序递增的独一无二的ID,大小6个字节,特别注意删除被认为是更新,对这条记录有个标志位标记为已删除。DB_ROLL_PTR
:回滚指针,指向改行的回滚段rollback segment
,大小为7个字节,通过这个找到之前版本的数据。该行所有旧版本数据,在undo log
中通过链表组织。DB_ROW_ID
:行标识,隐藏的单调递增ID,如果没有主键会生成这个当作主键,并以它产生聚簇索引,大小为6字节。
undo log
我们要对一条记录进行更改的时候(insert,update,delete),都需要把回滚时所需要的东西记录下来,InnoDB
把这些因为回滚而记录的东西称之为undo log
。
undo log
分为插入undo log
和更新undo log
(删除被视为更新),插入undo log
只在事务回滚时需要,事务提交后可以立即删除;更新undo log
也可以用来进行一致性读,只有当没有事务的时候才能被丢弃,应为需要这个生产快照进行一致性读取
每次对记录的改动都会生成一个log,每个log也有DB_ROLL_PTR
属性,可以将这些log链接起来,形成一个链表,也就是我们说的版本链,版本链的头节点就是记录最新的值,具体流程如下图:
READ_VIEW
什么是 Read View,说白了 Read View 就是事务进行快照读操作的时候生产的读视图 (Read View)。
它主要包含这几部分:
m_ids
:表示在生成ReadView
时当前系统中活跃(未提交)的读写事务的事务id
列表min_trx_id
:表示在生成ReadView
时当前系统中活跃的读写事务中最小的事务id
,也就是m_ids
中的最小值。max_trx_id
:表示生成ReadView
时系统中应该分配给下一个事务的id
值。 注意max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比方说现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时,m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4。creator_trx_id
:表示创建该ReadView
的事务的事务id
。
有了READ_VIEW之后,我们就可以遵循以下算法判断我们访问的某个版本(trx_id
)是否可见:
trx_id == creator_trx_id
,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。trx_id < min_trx_id
,表明生成该版本的事务在当前事务生成ReadView
前已经提交,所以该版本可以被当前事务访问。trx_id > max_trx_id
,表明生成该版本的事务在当前事务生成ReadView
后才开启,所以该版本不可以被当前事务访问。min_trx_id < trx_id < max_trx_id
,那就需要判断一下trx_id
属性值是不是在m_ids
列表中,如果在,说明创建ReadView
时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView
时生成该版本的事务已经被提交,该版本可以被访问。栗子:假设 m_ids=[5,6,7,9,12],如果trx_id=5,说明创建ReadView
时生成该版本的事务还是活跃的,该版本不可以被访问;如果trx_id=8,说明创建ReadView
时生成该版本的事务已经被提交,该版本可以被访问。
快照读和当前读
- 快照读:读取的是快照,默认隔离级别下
select
查询基本都是快照读 - 当前读:读取的是最新的,
select lock in share mode
、select for update
和update/delete/insert
都是读最新
工作流程
首先,我们应该明确mvcc只会在两种事物隔离级别起作用,READ COMMITED
和REPEATED_READ
。
READ COMMITED
下,同一事务每个快照读都会生成并获取最新的READ_VIEW;REPEATED_READ
下,同一个事务中的第一个快照读才会创建READ_VIEW,之后的快照读获取的都是同一个READ_VIEW。
如图所示,有如下一个版本链,并且准备执行事务。
- 第一次执行select语句时,由于是快照读,会产生一个READ_VIEW
- 根据READ_VIEW从版本链中找到合适的可见版本,这里是age=2的版本
- 执行update语句,生成age=3的版本
- 再次执行select语句,这里分两种情况:RR隔离级别,读取的是age=2的版本;RC隔离级别,读取的是age=3的版本
相关问题
RR是如何在RC级的基础上解决不可重复读的?
由于RR级别的快照读只会第一次在事务开启时时候生成READ_VIEW,在这个事务内快照读读取的数据都是一样的;RC级别时每次快照读都会重新生成READ_VIEW,所以同一事务下读取的结果不一样,这就是不可重复读。所以,避免不可重复读的方式就是生成READ_VIEW的策略不一样
- RR级别下:
事务A | 事务B |
---|---|
开启事务 | 开启事务 |
快照读(无影响)查询金额为500 | 快照读(无影响)查询金额为500 |
更新金额为400 | |
提交事务 | |
select 快照读 金额为500 | |
select lock in share mode当前读 金额为400 |
- RC级别下:
事务A | 事务B |
---|---|
开启事务 | 开启事务 |
快照读(无影响)查询金额为500 | 快照读(无影响)查询金额为500 |
更新金额为400 | |
提交事务 | |
select 快照读 金额为400 | |
select lock in share mode当前读 金额为400 |