MVCC(multi version concurrency control)多版本并发控制,众所周知MySQL中InnoDB的MVCC主要是实现InnoDB事务的关键机制,比如实现事务隔离级别中的可重复度和读已提交。
事务的隔离级别
在锁隔离级别前还需要了解一下当前读和快照读,事务隔离级别一般是指在快照读基础上进行,在使用共享锁和排它锁的时候使用的是当前读,读取的最新的已提交的数据。
当前读:读取最新的数据,在使用select for share,select for update 、 update、 insert 、delete,就是当前读读取的是最新的数据还会对数据进行加锁,就是平时说的共享锁和排他锁
快照读:是指只能看到读取第一次读取之前的数据,在读取之后再发生的更新不会被读取到。
既然是为了事务的隔离级别而努力那就要先来看一下MySQL的事务隔离级别
先解释一下脏读、不可重复读、幻读的定义
脏读:读取到其它事务没有提交的数据
不可重复读:自己没有改变的情况下,读取的数据与前一次读取的数据不一致,也就是不能读取到其它事务对此数据的变更
幻读:当本事务读取某一范围的数据行时,其它事务又在该范围内插入了新行,当前事务再读取该范围的数据行时,会发现有新的行(MySQL官方叫“Phantom Rows”)这就叫幻读。
读未提交
就相当于是读取最新的数据,不管这个数据的事务有没有提交。
脏读:会发生,查询到其它事务未提交的事务,其他事务可能根本不会提交。
不可重复读:会发生,每次都是读取的最新的甚至是未提交的数据,每次都可能会读取到其它事务的数据,每次数据都可能不一样。
幻读:会发生,因为读取的时候读取到了未提交的数据,与前一次查询出来的数据条数可能会增减。
读已提交
这个隔离级别下只能读取到所有已提交的最新的数据。
脏读:不会发生,查询出来的数据都是已经提交的数据。
不可重复读:会发生,每次都是读取的最新的已提交的数据,期间有其他事务修改数据提交后也会查询出来。
幻读:会发生,只要是已经提交的数据都能查询出来,期间其他事物插入的数据也能查询出来。
可重复读
这隔离级别下通过MVCC来实现可重复读每次读取自己事务的版本
脏读:不会发生,查询出来的数据都是其他事务已提交的数据
不可重复读:不会发生,其通过MVCC机制来实现哪怕该行数据被修改过,也能查询出来上次查询的版本的数据
幻读:会发生(普通情况下不会发生),解析见↓
正常不会发生幻读的情况:
事务1在事务2插入了符合自己查询条件的数据之后也就是第4步查询还是结果还是和第一次查询的数据结果相同没有发生幻读
会发生幻读的情况:
可以看到第7步结果包含了事务2插入的数据,因为事务1在更新的时候更新的数据包含了事务2的数据,在第7步查询出来的结果就将事务2插入的数据也查询出来了比第2步查询的结果多了一行,发生了幻读
Serializable(“串行化”或“可序列化”不知道用那个翻译)
此级别类似于 REPEATABLE READ,但如果禁用自动提交,InnoDB 会将所有普通 SELECT 语句隐式转换为 SELECT ... FOR SHARE。那么实际使用中都是手动开启了事务相当于是禁用了自动提交,没有手动开启事务的情况就是单独的一条SQL没加锁的情况下可以直接执行,比如查询。但是修改数据就要看其它事务有没有锁了。
值得注意的是很多网上的说这个隔离级别下事务是顺序执行很有矛盾,如果没有锁冲突(这里主要指的是上面说的隐式加的共享锁)是可以同时执行的,不知道是因为那些人的版本和我不一样还是没有去验证“顾名思义了”,我实验的版本是MySQL8.0.34,也不想去试老版本了各位注意甄别。
脏读:不会发生,它和可重复读一样不会读取未提交的数据。
不可重复读:不会发生,查询的时候加了共享锁,其它事务无法修改数据故不存在这个问题。
幻读:不会发生,原因同上。
隐藏字段
如文章开头所说MVCC(multi version concurrency control)多版本并发控制,可以知道是多版本的,那么如何实现多版本的这就是一个首当其冲的问题,数据库引擎自动的MVCC不像我们编程时的乐观锁那样增加版本号字段,我们家的版本号InnoDB也无法识别。其实隐藏字段就是其解决方法,它有DB_TRX_ID(最后一个操作的事务ID)、DB_ROLL_PTR(回滚指针)、DB_ROW_ID(行ID)
DB_TRX_ID:表示最后一次操作的事务ID,可以标识当前数据时那个事务在操作。事务id是在开启事务时分配的一个自增的标识。
DB_ROLL_PTR:回滚指针标识如果要回滚的话上一个版本的数据的标识,用于维护历史版本链,数据在UndoLog中
DB_ROW_ID:行的标识,如果没有设置主键那么MySQL就会设置一个隐藏列来作为主键
然后回到前面说的MVCC如何解决事务隔离级别的,前面也说到了在对数据进行insert、delete、update时使用的是当前读不存在隔离的问题读取的是最新的已提交的数据。
那么在进行快照读的时候MVCC就需要解决各个事务隔离的问题,这个时候就引出了一个叫ReadView(读视图)的东西在进行快照读时由ReadView决定最后读取到的数据是什么样。ReadView是一个概念,他在第一次读取创建,它属于一个事务,也就是由事务创建,创建的时候维护了创建那一刻活跃的事务,在读取的时候控制能读取到那些事务的数据。
其中读视图依靠几个关键变量来实现:
当前系统中活跃的读写事务id列表
活跃的最小事务id
创建这个ReadView的事务id
下一个要分配的事务id(最大事务id)
上面几个数据在创建ReadView那一刻决定
然后通过下面的逻辑从UndoLog链依次往下进行判断是否可读取该数据,匹配到之后就结束
数据版本事务id=创建ReadView的事务id,则可以读取该数据
数据版本事务id<活跃的最小事务id,表明在读取数据之前该事务就已经提交了则是可以读取的
数据版本事务id≥系统中最大的事务id,表明这个事务是在ReadView创建之后操作的不可以读取
活跃的最小事务id<=数据版本事务id<=系统中最大的事务id,则要看数据版本事务id在不在活跃的事务中,如果在活跃的事务中则表明创建ReadView时该事务没有提交不可以读取该数据,如果数据版本事务id在在活跃的事务中则表明创建ReadView时该事务已经提交可以读取该数据。
根据上面的规则再加上隔离级别的判断就可以实现各个隔离级别下快照读的数据隔离了。
评论区