7. MVCC

118 阅读4分钟

7. MVCC.png

1. 隔离级别

MySQL 有四个隔离级别:

  • READ-UNCOMMITTED(读未提交)
  • READ-COMMITTED(读已提交)
  • REPEATABLE-READ(可重复读)
  • SERIALIZABLE(可串行化)

MySQL 中事务隔离级别是通过参数 transaction_isolation 来控制的,该参数为 global,session,也就是说每个 session 可以自己设置隔离级别,多个隔离级别可以混用,那么隔离级别之间的影响是怎样的呢?

  • 每个线程(会话)试图影响其他线程的行为,是以自己的隔离级别为准的
  • 每个线程(会话)被其他线程影响的行为,以其他线程最高的事务隔离级别为准

举例说明,一个线程 RC,一个线程 RR,RR 执行语句锁全表数据

begin;
select * from t for share;

查看锁情况为: image.png

可以看到,虽然有其他线程隔离级别为 RC,但是本线程加锁还是按照本线程隔离级别来的

此时,RC 线程如果想插入一条数据,会发现被间隙锁锁住,这就是我们所说的被其他线程影响的行为,以其他线程最高隔离级别为准,这里也就是以 RR 为准,间隙锁有用,如下: image.png

可以看到,RC 线程在想插入数据时,被 RR 线程的间隙锁堵住了,如果不解锁,最后会超时 image.png

2. 多版本物理形态

MVCC(Multi-Version Concurrency Cntrol,多版本并发控制)其本质是通过 undo 来实现的,undo 的物理形态可以参考下图 image.png innodb 每行数据都有三个隐藏字段:row_id、roll_ptr、trx_id,其中 roll_ptr 和 trx_id 就是我们实现 MVCC 的关键

roll_ptr 存放的是上一个版本该行记录的上一个版本,通过它可以找到该行数据之前的历史版本,也就是图中的 U3、U2、U1,即 undo log

trx_id 存放的是修改该行最新的事务 id,每个事务启动一致性视图(start transaction with consistent snapshot 或者事务中执行快照读)时,会生成一份当前活跃的事务数组,该数组内的事务 id 以及该数组最大事务 id 以后的事务所做的修改,对本事务均不可见,通过该规则,可以判断是否需要回滚

上图中数据库中此时保存的数据是 V4 版本数据,以及 U1、U2、U3 这三个 undo,主键索引查询时如果事务 id 小于这几个或者这几个事务 id 在不可见事务数组,或者这几个事务 id 大于当前事务 id,那么表明当前事务读取该行时需要走 undo 计算逻辑

对于二级索引,每个索引所在的页上有一个事务 id,该事务 id 表示修改当前页(二级索引页)的最新的事务 id,整页数据共享,判断条件跟主键一致,如果需要回滚,那么需要回表到主键进行查询(如果覆盖索引能完全用上,也会回表)

3. 一致性视图

通过 MVCC 可以在 RR 或者 RC 级别下获得一致性视图,那么一致性 视图是什么时候开始创建的呢?

3.1 RR 隔离级别

在 RR 级别下,一致性视图在事务首次执行快照读时创建,也就是不加任何锁的查询语句,如下:

begin;
select …… from …… for share/update   // 当前读,不创建一致性视图
update …… set ……   //  当前读,不创建一致性视图
select …… from ……   // 快照读,创建一致性视图

除了使用begin开启事务外,通过语句start transaction with consistent snapshot可以直接开启事务并同时创建一致性视图

3.2 RC 隔离级别

在 RC 隔离级别下,一致性视图是在每次执行快照读时添加,读完就释放

如果是存储过程中有两个 select,那这两个 select 算一个语句吗?答案是不算,每个 select 都创建一个一致性视图

如果一个 select 中有 union 或者 union all 呢?union 前后两个 select 共享一个一致性视图吗?答案是是的,不止是 union,join 也一样,甚至两张表使用不同事务引擎,比如一个使用 InnoDB,一个使用 TokuDB,一致性视图也是共享的