MVCC(Multi-Version Concurrency Control),即多版本并发控制。不使用锁,主要是用来提高数据库的并发性能;算是一种概念,不同的数据库有不同的实现方式,本文主要介绍mysql的innodb引擎中的实现方式。
在mysql的innodb中,前面我们有篇文章《Mysql系列(二)Mysql事务四大隔离级别详解&演示》分析了4种隔离级别,以及每种隔离级别下导致的问题,脏读、不可重复读、幻读。其中不可重复读、幻读就是使用MVCC来解决的。
首先锁的存在,目的是为了在并发场景下,保持数据的安全、一致。
并发场景有:
进行并发控制,常规手段就是加锁,不管是咋java业务代码中,还是mysql数据库本身,都有实现自己的锁,其中mysql的锁有以下几种:
除了这3种锁,还有乐观锁、悲观锁、记录锁、自增锁、意向锁;
既然可以使用行锁、表锁、间隙锁来保证数据操作的安全性,那么还要MVCC的出现是为何呢? 实际是因为在性能方面还有优化的空间。
虽然使用锁可以保证数据安全,但是毕竟加了锁就意味着并发性能的降低,因此,能不使用锁就尽量不使用锁。在某些场景下,MVCC可以在比使用锁更快。
在读-读、读-写、写-写这3种并发场景中,读-写 可以不使用锁,而是使用MVCC来实现数据的并发操作以及安全一致性。
因此,mysql是同时使用了MVCC+行锁、表锁、间隙锁来保证了数据安全,又尽可能大的实现了性能最优化。
那么不使用锁,MVCC是如何更高效的解决读-写这种并发场景下的数据安全呢?
我们知道,事务在执行失败时,会将数据回滚为上个版本,而MVCC叫做多版本并发控制,核心概念就在版本上,也就是说数据库存储了多个版本的数据。
多个版本整体上分为两类:最新版本、历史版本。
这也牵扯出来另外两个概念:
那么多个版本,mysql是如何存储的呢?
innodb存储引擎中,我们存在表中的数据,除了我们设置的业务字段,另外还有3个默认字段,如下图末尾3个:
其中,DB_ROLL_PTR结合undo log实现。
undo log称为回滚日志,是InnoDB MVCC事务特性的重要组成部分,存在形式就是一种日志文件。
undo log的数据结构,非常复杂,可以简单理解为链表,链表头部存储最新版本,尾部存储最早版本。通过遍历链表,就可以找到对应版本的数据。
大多数对数据的变更操作包括INSERT/DELETE/UPDATE,其中,
只有undolog,还不足以实现mvcc,因为既然undolog维护了那么多版本,遍历的时候,应该去找哪一个版本呢?这里必然牵扯到一种规则,这种规则在不同的数据库隔离级别下是不一样的。
innodb具体实现的时候,使用了一种叫做readview的定西。
readview称为读视图,是事务在进行快照读的时候产生的,算是一种数据结构。
readview中包含3个参数:
每个事务在进行自己的快照读时,都会产生自己事务对应的readview。
例如下图中:
有4个事务同时在执行,事务id分别为1,2,3,4;其中,1,2,3还没有提交,4已经提交,那么当事务2在进行快照读时,产生一个readview,其trx_list存的就是活跃的1,2,3,如下图:
此时,最小的事务id是1,最大的事务id是5 (尚未分配的下一个id),当前最新的事务id是4 (事务4的id)。
可见性算法
上图中右侧黄色部分就是本次查询到底该取哪个版本数据的规则,也叫可见性算法;
查找过程:
遍历undolog的最新数据到最老数据,逐个判断每条log的事务id,当作DB_TRX_ID,然后使用上图中黄色可见性算法比对,最终确定当前遍历的数据是不是目标数据。
读视图的产生时机
在不同的隔离级别下,readview产生的时机是不同的: