基础概念:
MVCC:全称为Multi-Version Concurrency Control,即多版本并发控制,是基于数据版本对并发事务进行访问。
当前读:像select in share mode, select for update, update, insert, delete这些操作都是当前读,就是读取的记录是最新的版本。读取时还要保证其它事务不能修改当前记录,会对该记录加锁。
快照读:就是普通的select操作SQL语句,不会对记录加锁。快照读的前提是隔离级别不能是串行级别,否则会退化成当前读。
ReadView:是快照读SQL执行时MVCC提取数据的依据。它是一个数据结构,包含四个字段:
m_ids:当前活跃的事务编号集合
min_trx_id:最小的活跃事务编号
max_trx_id:预分配的事务编号,即当前最大事务编号+1
creator_trx_id:ReadView创建者的事务编号
MVCC的实现原理:
MVCC的实现要基于undolog(回滚日志)版本链和ReadView以及记录中的三个隐藏字段。
三个隐藏字段:
db_trx_id:最近修改事务的id,记录创建这条记录或者最后一次修改这条记录的事务id。占6个字节
db_roll_ptr:回滚指针,指向这条记录的上一个版本,用于配合undolog。占用7个字节。
db_row_id:隐藏的主键,如果表中没有主键,innodb会自动生成一个6字节的row_id.
(其实,还有一个隐藏字段,删除标记)
RC(读已提交)隔离级别下:在每一次快照读时,都会生成一个全新的ReadView
RR(可重复读)隔离级别下:连续多次快照读时只在第一次快照读时,都会生成ReadView
若一条记录经历了以下过程
那么此时生成undolog版本链则是以下这样:
undolog版本链使用规则:
沿着版本链依次判断:
1、当前版本链事务id(trx_id)是否等于creator_trx_id,若相等则说明读取操作的事务与最近修改的事务为同一事务,此时肯定可以访问的。
2、判断trx_id 是否小于min_trx_id,若小于,则说明该事务已经提交,可以访问。
3、判断trx_id 是否大于max_trx_id,若大于,则说明该事务是在ReadView生成后才开启的,则不允许访问。
4、判断 min_trx_id <= trx_id <= max_trx_id 是否成立,若成立则判断该事务id是否在当前活跃的事务编号集合(m_ids)中,若不存在则说明是已提交事务,则允许访问,否则不允许访问。
下面将上例带入规则
RC隔离级别下:
第一次Select 查询生成的
ReadView:m_idx={2,3,4}, max_trx_id=5,min_trx_id=2,cretor_trx_id=4
带入版本链:
(1.1) trx_id(3)不等于cretor_trx_id(4),不允许访问。
(1.2) trx_id(3)不小于min_trx_id(2),不允许访问。
(1.3) trx_id(3)不大于max_trx_id(5),不允许访问。
(1.4) min_trx_id(2) <= trx_id(3) <= max_trx_id(5) 成立,但在活跃事务编号集合中,则不允许访问
【其实,此时事务3的执行操作还未开始,并不存在于版本链中,大家理解即可】
-----------------------------------------------------------------------------------------------------------------------
(2.1) trx_id(2)不等于cretor_trx_id(4),不允许访问。
(2.2) trx_id(2)不小于min_trx_id(2),不允许访问。
(2.3) trx_id(2)不大于max_trx_id(5),不允许访问。
(2.4) min_trx_id(2) <= trx_id(2) <= max_trx_id(5) 成立,但在活跃事务编号集合中,则不允许访问
-----------------------------------------------------------------------------------------------------------------------
(3.1) trx_id(1)不等于cretor_trx_id(4),不允许访问。
(3.2) trx_id(1)不小于min_trx_id(2),不允许访问。
(3.3) trx_id(1)不大于max_trx_id(5),不允许访问。
(3.4) min_trx_id(2) <= trx_id(1) <= max_trx_id(5) 成立,但不在活跃事务编号集合中,则允许访问
故第一次select 结果是name为张三的记录。
第二次select生成的ReadView:
ReadView:m_idx={3,4}, max_trx_id=5,min_trx_id=3,cretor_trx_id=4
带入版本链可得结果是name为李四的记录。
RR隔离级别下:
同理可知,两次查询结果均是name为张三的记录。
备注:第一次查询时,事务3还没执行,故在版本链中是存在。本着省事的原则就不再画了。