前言
MVCC版本控制是一种Mysql实现隔离级别的机制,其利用版本链以及对应的undo日志,通过快照读取的方法来控制各个级别的事务所能够读取到的信息。
从事务隔离级别问题来说,MVCC能够解决脏读,不可重复读的问题,但是对于幻读则无能为力,所以这里也会顺带总结一下关于数据库幻读的解决方法,便于读者整理完整的思路。
作者本身对于Mysql理解有诸多不足,如果有表述错误或者不当的地方,请及时指出,非常感谢您的阅读。
隔离级别
Mysql提供四隔离级别,分别用来解决四种事务一致性的问题。
那么问题来了,为什么需要需要隔离级别,为什么使用锁技术来让事务串行执行。
答案很简单,效率。
MySql是一个服务器,那么就必须支持多个连接。因此Mysql会为每一个连接都去维护一个session,每个客户端在该session中进行数据的读写请求,那么必然会同时出现多个事务操作同一条语句的问题。如何保证数据的一致性最简单的方法就是让多个事务串行执行,但是在高并发下,这会导致大量的事务堆积,成本上是无法接受的。
因此,Mysql的设计者提出了隔离级别的概念,也就是说。
Mysql的隔离级别是通过牺牲一部分隔离性来保证事务的并发执行效率
关于四种隔离级别和解决的问题这里不赘述,网上资料大把大把,主要来说在MVCC下的版本链和readView
版本链
在InnoDB中,一张表必须包含两个字段,trx_id
和roll_pointer
。
- trx_id : 事务字段,当一个事务去操作某个行的数据时,会将自己的事务Id赋值给trx_id字段
- roll_pointer : 回滚指针,当一个事务更新了一个字段的时候,并不会直接删除掉之前的字段,而是将该指针指向之前的字段存储到undo blog
用一张图来解释一下。
每当事务中更新一条数据时,都会将其添加到undo blog
中的,随着更新的次数增多,数据会逐渐被连接成一个链,也就是所说的版本链。
ReadView
实际上,MVCC版本控制主要就是靠版本链与ReadView来维护的。
我们可以将Read View看作一个数组,整个数组的左边界和右边界时当前活跃事务的事务Id。举个例子 :
现在存活事务有事务100,150,200,250
那么Read View就是{100,150,200,250}
//查询事务Id的语句
SELECT tx.trx_id
FROM information_schema.innodb_trx tx
WHERE tx.trx_mysql_thread_id = connection_id()
这个事务Id也就是对应着版本链中的trx_id
,那么它的作用是什么呢?
首先我们来看两个最极端的隔离级别,READ UNCOMMITTED
和SERIALIZABLE
。当隔离级别为读未提交时,Mysql是不做任何隔离性的要求,所以这个时候只需要读取最新的数据即可,这也是脏读产生的原因。同理,串行化的级别是最严苛的事务隔离界别,在这个级别下,是通过加锁的方式对事务进行串行化执行,所以这两个级别是不会使用到版本链以及Read View。
那么剩下的两个级别都使用到了这两个模块,其中有一些细微差异,我们放在之后说,首先来说下是如何进行操作的。
事务执行过程中,只有在第一次真正修改记录时(比如使用INSERT、DELETE、UPDATE语句),才会被分配一个单独的事务id,这个事务id是递增的
上面提到生成的Read View是当前所有活跃事务的事务Id的集合,那么这个集合就有个范围,所以可以通过事务Id来判断这些事务生成时间顺序。下面来看下当一个事务查询一条记录的时候究竟会发生什么。