大家想一下,如果要解决读一致性的问题,保证一个事务中前后两次读取数据结果一致,实现事务隔离,应该怎么做?因为在InnoDB里面,所有的活动都是运行在事务里面的,如果autocommit=1,每个SQL语句都是一个事务,所以这个问题也可以这么问:MySQL如何实现并发控制?
总体上来说,我们有两大类的方案:LBCC 和 MVCC。本篇我们先来看 MVCC(Multi Version Concurrency Control,多版本的并发控制)。
1.MVCC是什么
MVCC的核心思想是: 我可以查到在我这个事务开始之前已经存在的数据,即使它在后面被修改或者删除了。在我这个事务之后新增的数据,我是查不到的。
这个快照什么时候创建?读取数据的时候,怎么保证能读取到这个快照而不是最新的数据?这个怎么实现呢?
InnoDB为每行记录都实现了两个隐藏字段:
- DB_TRX_ID,6字节:插入或更新行的最后一个事务的事务ID,事务编号是自动递增的(我们把它理解为创建版本号,在数据新增或者修改为新数据的时候,记录当前事务ID)。
- DB_ROLL_PTR,7字节:回滚指针(我们把它理解为删除版本号,数据被删除或记录为旧数据的时候,记录当前事务ID)。我们把这两个事务ID理解为版本号。
2.MVCC示例
1.第一个事务,初始化数据。此时数据表中,创建版本是当前事务ID,删除版本为空。
==> 第二个事务,执行第1次查询。
读取到两条原始数据,这个时候事务ID是2。
2.第三个事务,插入数据。此时的数据,多了一条tom,它的创建版本号是当前事务编号3。
==> 第二个事务,执行第2次查询。
MVCC的查找规则:只能查找创建时间小于等于当前事务ID的数据,且删除时间大于当前事务ID的行(或未删除)。
==> 当前事务ID=2,所以只能查找新增事务ID<=2的数据。也就是不能查到在我的事务开始之后插入的数据,wangwu的创建ID大于2,所以还是只能查到两条数据。
3.第四个事务,删除数据,删除了id=2 jack这条记录。此时的数据,李四的删除版本被记录为当前事务ID(4),其他数据不变。
==> 第二个事务,执行第3次查询。
查找规则:只能查找创建时间小于等于当前事务ID的数据,和删除时间大于当前事务ID的行(或未删除)。
==> 当前事务ID=2,所以只能查找删除事务ID>2的数据。也就是,可以查到在我事务开始之后删除的数据,所以jack依然可以查出来。所以还是这两条数据。
4.第五个事务,执行更新操作,这个事务事务ID是5:
==> 第二个事务,执行第4次查询:
查找规则:只能查找创建时间小于等于当前事务ID的数据,和删除时间大于当前事务ID的行(或未删除)。
==> 当前事务ID=2,所以就是可以查找新增事务ID<=2和删除事务ID>2的数据。因为更新后的数据 zhaoliu 创建版本大于 2,代表是在事务之后增加的,查不出来。而旧数据 zhangsan的删除版本大于2,代表是在事务之后删除的,可以查出来。
通过以上演示我们能看到,通过版本号的控制,无论其他事务是插入、修改、删除,第一个事务查询到的数据都没有变化。
3.MVCC小结
上面演示的直接select都是快照读,读取的是记录数据的可见版本(可能是过期的数据),不用加锁。
而 insert/delete/update 在操作数据前的查找是当前读,即读取并操作的是记录数据的最新版本。举个例子,比如上面再 update mvcctest set name=left(name,4),最后的 name 是创建版本=5的zhao 而不是 zhan,所以能够保证最终事务结束后的结果是正确的。
那么,问题来了,如何保证在事务读取当前记录,然后操作的这段时间内,数据不被修改?答:加锁,MVCC+LBCC 搭配使用。保证其他事务不会再并发的修改这条记录。
关于 LBCC 请参考下一篇文章…