在sql标准下只有串行化的隔离级别才能解决幻读,但是这种读写都互斥的行为肯定影响性能,MVCC大名鼎鼎是因为它可以在不可重复读的隔离级别下"杜绝了幻读的问题",大大增加了mysql的读写并发性能。
额,"杜绝了幻读的问题"这里为什么打引号?首先我个人理解的幻读分为两种情况,事务A查询某个范围的数据,(注意是范围,具体某条就是重复读的问题了),事务B这时在这个范围插入了数据并提交了事务,那么事务A这时再次查询这个范围,数据条数对不上了(产生了幻觉),这个我称之为幻读的读问题;还是一样,事务A查询某个范围的数据,事务B这时在这个范围插入了数据并提交了事务,然后事务A也插入了同样的数据,插入失败(也产生了幻觉),我称这个为幻读的写问题。为什么打引号,是因为MVCC只解决了幻读的读问题。
弄清MVCC机制首先要先了解下undo log,undo log用于事务回滚的,事务提交后,按理说不用回滚了,可以删了,但是它不会删,这就是留给MVCC用的,但是insert 的undo log会删,这个影响不大,因为它创造了数据,不会有并发的问题(写、写肯定时互斥的)。那么这个undo log 不删除就记录一个从1递增的号码,每次修改后面的undo会记录前面undo log的号码,形成了一个undo log链表。
另外一个就是MYSQL每条数据有三个隐藏字段:
DB_ROW_ID: 如果没有为表显式的定义主键,并且表中也没有定义唯一索引,那么InnoDB会自动为表添加一个row_id的隐藏列作为主键。
DB_TRX_ID︰每个事务都会分配一个事务ID,当对某条记录发生变更时,就会将这个事务的事务ID写入trx_id中。
DB_ROLL_PTR:回滚指针,本质上就是指向undo log的指针。(一个事务多个操作的话,undo log就与多条,它里面存的一个字段就指向上一条undolog,这样就形成了一个链表,回滚指针记录的最新的undo log的指针)

通过DB_ROLL_PTR就能找到undo log链表了。如果空的说明没有并发,就你查,很简单,直接查表就好;那么链表不为空说明有别的事务在修改或者已经修改好了,那我们就从这个链表倒着找我们要是的数据,那这个数据能不能被查看到呢?这个就需要依靠readview了,
MVCC的机制就是一个事务开启后,这个瞬间会生成一个readview这里还有一个东西叫ReadView ,这个ReadView中主要包含 4 个比较重要的内容,分别如下:
-
creator_trx_id,创建这个 Read View 的事务 ID。说明:只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为事务分配事务id,否则在一个只读事务中的事务id值都默认为 0 。
-
trx_ids,表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。 -
up_limit_id,活跃的事务中最小的事务 ID(什么是活跃?就是还没提交或者回滚的事务)。 -
low_limit_id,表示生成ReadView时系统中应该分配给下一个事务的id值。low_limit_id是系统最大的事务id值,这里要注意是系统中的事务id,需要区别于正在活跃的事务ID。
注意:low_limit_id并不是trx_ids中的最大值,事务id是递增分配的。比如,现在有id为 1 ,2 , 3 这三个事务,之后id为 3 的事务提交了。那么一个新的读事务在生成ReadView时,trx_ids就包括 1 和 2 ,up_limit_id的值就是 1 ,low_limit_id的值就是 4 。
ReadView的规则
有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见。
-
如果被访问版本的trx_id属性值与ReadView中的
creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。 -
如果被访问版本的trx_id属性值小于ReadView中的
up_limit_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。 -
如果被访问版本的trx_id属性值大于或等于ReadView中的
low_limit_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。 -
如果被访问版本的trx_id属性值在ReadView的up_limit_id和
low_limit_id之间,那就需要判断一下trx_id属性值是不是在trx_ids列表中。 -
如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问。
-
如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问
但还是有幻读的写问题,这个mvcc管不了,innodb是通过间隙锁来控制的,简单来说就是锁住这个范围内的数据,不给插入,这样来解决幻读的写问题。