多事务操作同一行数据的时候,就会出现各种并发问题,MySQL通过四种隔离级别来解决这些问题,
读未提交隔离级别是最宽松的,基本没有做隔离,所以实现起来很简单;
读提交隔离级别是每次执行语句(包括查询和更新语句)的时候都会生成一个一致性视图,从而保证当前事务可以看到其他事务提交后的数据;
可重复读隔离级别的实现是每个事务在开启的时候都会生成一个一致性视图,当其他事务有提交后也不会影响当前事务中的数据,要保证这一点mysql是通过多版本控制机制MVCC来实现的。
可串行化隔离级别的隔离级别比较高,是通过加锁来实现,所以mysql有一套锁机制。
读提交和可重复读隔离级别都是依赖于MVCC多版本控制机制实现,今天我们就来讨论mysql中的MVCC多版本控制机制
MVCC机制是通过read-view机制与undo log版本链比对机制,使得不同的事务会根据数据版本链对比规则读取同一条数据在版本链上的不同版本数据。
事务在开启的时候首先会申请一个事务id:ransaction id
事务对某行数据做修改操作的时候,Mysql会保留修改前的数据undo回滚日志,并且把事务id:ransaction id赋值给版本记录中的字段trx_id。
把这些undo log日志串联起来形成一个历史记录版本链,如图
注意这里的版本记录不是真实物理存在的,真实物理存在只有最新的一条记录,其他历史记录都是通过回滚日志推导出来的。
可重复读隔离级别和读提交隔离级别是通过生成一个一致性视图来实现,这个一致性视图就是read-view。
一个事务启动的时候,innodb会为这个事务构造一个数组,用来保存这个事务的启动瞬间正在活跃的所有事务id。“活跃”指的是启动了,但是没提交。
数组里面id最小的值即为低水位,最大的值+1记为高水位,这便是一致性视图。
每个事务在做查询的时候会根据一致性视图的可见性规则去undo log版本链中推导对应的数据。
a. 若 row trx_id在数组中,表示这个版本是由还没提交的事务生成的,不可见;
b. 若 row trx_id不在数组中,表示这个版本是已经提交了的事务生成的,可见。
图中事务A查询的i是什么?我们先来分析一下。
按照从上到下事务开启的顺序,每个事务对应的一致性视图如下:
事务A的一致性视图数组[11]
事务B的一致性视图数组[11,12]
事务C的一致性视图数组[11,12,13]
事务A在查询的那个时刻,undo log版本链是:
{trx_id=11,id=1,i=10,roll_pointer=0}>>>{trx_id=13,id=1,i=11,roll_pointer=1}>>>{trx_id=12,id=1,i=12,roll_pointer=2}
“{}” 代表的是版本记录
“>>>” 代表的是回滚日志undo log
事务A在查询的时候,事务B和事务C属于未来事务,对事务A是不可见的。因此事务A查询的数据是通过最新的数据记录根据undo log不断向前回滚才得到的数据:i=10。
事务A查询1结果是什么?
事务A查询2结果是什么?
由一致性视图可见性规则分析,对事务A来说,事务B是未来事务,对事务A是不可见的,因此查询结果i=10。
查询2的结果i=12,为什么呢?先来看看两个概念
在可重复读隔离级别中这种通过回滚日志找到对应版本记录的读取方式就是一致性读
而不需要回滚,永远只需要读取最新版本记录的方式就是当前读
事务中如果有更新语句的话,更新语句都是以当前读的方式读取到版本记录中最新数据,然后再进行更新操作,因此上图中的查询结果为i=12。
下面两种查询方式也是当前读:
select k from t where id=1 lock in share mode;
select k from t where id=1 for update;
以上便是MVCC机制,按照其规则,这种机制只有在可重复读隔离级别和读提交隔离级别下才会有。