你一定要了解的MySQL知识 - MVCC

179 阅读4分钟

往期精彩

你一定要了解的MySQL知识 - 锁

全称Multi-Version Concurrency Control,多版本并发控制。

其思想就是保存数据的历史版本,通过对数据行的多个版本管理来实现数据库的并发控制,解决了读写阻塞避免死锁一致性读等问题。

MySQL的innodb引擎基于表隐藏字段undo logreadView来实现了MVCC

快照读与当前读

快照读是不加锁的读,语法形式为简单的SELECT,如

 -- 快照读 
 select * from goods;

与快照读对应的就是当前读,语法形式为:

 -- 当前读
 select * from goods where id=1 lock in share mode;
 select * from goods where id=1 for update;

当前读就是读最新数据,需要加锁

表隐藏字段

MySQL表中除了用户自己定义的字段外,还会自动生成隐藏字段。如下:

字段名说明
DB_TRX_ID记录最近修改这条记录的事务ID(自增)
DB_ROLL_PTR回滚指针。配合undo log指向行记录的上个版本
DB_ROW_ID隐式主键。没有指定主键时自动生成

可以使用ibd2sbi xxx.ibd命令查看

undo log

回滚日志,其以链表形式存储行记录的历史版本。大概如下图表现形式:

image.png

ReadView

ReadView是读取undo log中哪条数据的依据,每个事务拥有各自的ReadView,以下简称快照。包含4个核心字段

字段名说明
m_ids生成这个快照时处于活跃状态的事务ID的有序列表
m_up_limit_id低水位线,事务ID小于m_up_limit_id的事务都可见,意为m_ids中的最小值,小于m_up_limit_id的事务都已提交或回滚
m_low_limit_id高水位线,事务ID大于等于 m_low_limit_id 的事务都不可见,其意为下一个待分配的事务ID(大于所有已分配的事务ID)
m_creator_trx_id创建快照的事务ID

判断是否可见规则

ReadView中, 有一个changes_visible()方法,用于判断某个事务的更改对当前事务是否可见。

  1. 事务ID小于m_up_limit_id或等于m_creator_trx_id可见。
  2. 事务ID大于等于m_low_limit_id不可见。
  3. 事务ID小于m_low_limit_id并且m_ids为空时可见。
  4. 当事务ID∈ [m_up_limit_id, m_low_limit_id)时,如果事务ID存在m_ids中,说明事务未提交则不可见,如果不在则可见。

快照生成和关闭

快照的生成定义在ReadView* trx_assign_read_view()方法中:

  • 执行普通的SELECT会生成
  • 使用start transaction with consistent snapshot会生成,但此指令只在RR级别有效
  • 在RC级别中,快照在SELECT语句结束时关闭(每次SELECT都会生成最新的快照)
  • 在RR级别中,快照在事务结束时关闭(整个事务期间,复用第一次生成的快照)

判断是否可见流程

image.png

事务的ID是一个64位的非负整数,只有读写事务会分配事务ID,只读事务是不会分配ID的。

此流程针对主键索引或没有索引。

如上图所示,在RC级别中:(注意ReadView的变化哦)

  • 第一次查询,针对V1的数据,符合DB_TRX_ID 小于m_up_limit_id,所以可以查询到age=10的记录
  • 第二次查询,针对V2的数据,事务2修改了数据,所以undo log会添加一条数据。但是age=20的记录不满足可见规则,读到了age=10的数据(解决了脏读)
  • 第三次查询,针对V2的数据,事务2已提交,age=20的记录符合规则4,所以查到了age=20的数据(注意咯,不可重复读出现了)

上面是RC级别的分析,接下来看RR级别是怎么解决不可重复读的。

image.png

如上图所示,在RR级别中:(注意ReadView的变化哦)

  • 第一次查询,针对V1的数据,符合DB_TRX_ID 小于m_up_limit_id,所以可以查询到age=10的记录
  • 第二次查询,针对V2的数据,事务2修改了数据,所以undo log会添加一条数据。但是age=20的记录不满足可见规则,读到了age=10的数据(解决了脏读)
  • 第三次查询,针对V2的数据,事务2已提交,在RC级别中,age=20的记录符合规则4,但是RR级别中,其不满足所有规则,所以最终查到了age=10的数据(可重复读了!)

总结

MySQL基于MVCC实现了到RR级别的隔离性,如果需要解决幻读和达到串行化的隔离级别,则需要加锁。所以可以说MVCC+实现了隔离性。

image.png