浅谈MySQL:MVCC的作用及原理
什么是MVCC
MVCC全称是Multi-Version Concurrency Control,即多版本并发控制,主要是为了提高数据库的并发读写性能。
当我们并发读写同一行数据的时候,为了防止出错,需要对数据进行加锁操作,但这并不是一个高效的操作,很容易造成操作请求阻塞超时。而MVCC采用了更好的方式去处理并发读写请求,做到在发生读写请求冲突时不用加锁。这个读是指的快照读,而不是当前读。
那么什么是当前读,什么又是快照读?我们下面来学习一下。
当前读
当前读就是读取数据库记录的最新版本,会对当前读取的数据进行加锁,防止其他事务修改数据,是悲观锁的一种操作。
以下几种情况是当前读:
-
select 语句加锁是当前读
# 共享锁 select a from t where id = 1 lock in share mode; #排他锁 select a from t where id = 1 for update;
-
update 语句是当前读
update t set a = a + 1;
快照读
快照读的实现是基于多版本并发控制,即MVCC,快照读读到的数据不一定是当前最新的数据,有可能是之前历史版本的数据。
以下几种情况是快照读:
-
在默认隔离级别下,select 语句默认是快照读
select a from t where id = 1;
快照读与MVCC的关系
MVCC是”维持一个数据的多个版本,使读写操作没有冲突”的一个抽象概念。
这个概念需要具体功能去实现,这个具体实现就是快照读。
MVCC解决并发哪些问题
MVCC用来解决读-写冲突的无锁并发控制,就是为事务分配单向增长的时间戳。为每个数据修改保存一个版本,版本与事务时间戳相关联。读操作只读取该事务开始前的数据库快照。
解决问题如下:
-
并发读-写时:可以做到读操作不阻塞写操作,同时写操作也不会阻塞读操作。
-
解决脏读、幻读、不可重复读等事务隔离问题,但不能解决写-写更新丢失问题。因此需要下面提高并发性能的组合:
- MVCC + 悲观锁:MVCC解决读写冲突,悲观锁解决写写冲突
- MVCC + 乐观锁:MVCC解决读写冲突,乐观锁解决写写冲突
MVCC的实现原理
MVCC主要是版本链,undo日志 ,Read View 来实现的
版本链 & undo日志
我们数据库中的每行数据都有如下几个隐藏字段:
字段名 | 占用空间 | 说明 |
---|---|---|
trx_id | 6byte | 最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID |
roll_pointer | 7byte | 回滚指针,指向这条记录的上一个版本(存储于rollback segment里) |
row_id | 6byte | 隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以db_row_id产生一个聚簇索引 |
版本链的结构如下图所示,每次修改都会在这个链条后面增加一个版本,trx_id
就代表最新这条记录的ID。而历史数据都会被存入undo日志,通过回滚指针roll_pointer
可以找到数据行的前一个版本,以供事务回滚时使用。
Read View
事务进行快照读操作的时候生产的读视图(Read View),在该事务执行快照读的那一刻,会生成数据库系统当前的一个快照。通过Read View,事务可以判断版本链中的哪个版本是可用的。
Read View中保存的字段信息如下:
字段名 | 说明 |
---|---|
m_ids | 表示在生成Read View时,当前系统中活跃的读写事务的事务ID列表 |
min_trx_id | 表示在生成Read View时,当前系统中活跃的读写事务中最小的事务ID,也就是m_ids中的最小值 |
max_trx_id | 表示生成Read View时,系统中应该分配给下一个事务的ID值 |
creator_trx_id | 表示生成该Read View的事务的事务ID |
那么Read View是如何判断版本链中的哪个版本是可用的呢?通过以下4个条件就可以判断:
trx_id
==creator_trx_id
:可以访问这个版本trx_id
<min_trx_id
:可以访问这个版本trx_id
>max_trx_id
:不可以访问这个版本min_trx_id
<=trx_id
<=max_trx_id
:如果trx_id
在m_ids
中是不可以访问这个版本的,反之可以访问
MVCC如何实现RC和RR
我们在上一篇中讲过RC(Read Committed,读提交)和RR(Repeatable Read,可重复读)隔离级别的定义,这两个隔离级别的实现都离不开MVCC。
RR、RC生成Read View的时机
- RC隔离级别下,每个快照读都会生成并获取最新的Read View,因此可能出现在同一个事务中两次查询的结果不一致的情况
- RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View,之后的快照读获取的都是同一个Read View,之后的查询就不会重复生成了,所以一个事务的查询结果每次都是一样的
解决幻读问题
- 快照读:通过MVCC来进行控制的,不用加锁。按照MVCC中规定的“语法”进行增删改查等操作,以避免幻读。
- 当前读:通过next-key锁(行锁+gap锁)来解决问题的。
总结
从以上的描述中,我们可以看出MVCC指的就是在使用RC、RR这两种隔离级别的事务在执行普通的SEELCT操作时访问记录的版本链的过程,MVCC可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。