mysql之MVCC
1、概念
mvcc作用在于解决并发条件下,读写冲突的问题。一般用于RC和RR隔离级别,解决脏读和不可重复读的问题。
(1)当前读
读取的是记录的最新版本,读取时还要保证其他事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如:select ... lock in share mode(共享锁),select ... for update、update、insert、delete(排他锁)都是一种当前读。
(2)快照读
简单的select 就是快照读,读取的是记录的可见版本, 有可能是历史数据,不加锁,不阻塞
-
Read Committed:每次select,都会生成一个快照读
-
Repeatable Read:开启事务后第一个select语句才是快照读的地方。
-
Serializable:快照读会退化为当前读。
2、MVCC的条件
要实现MVCC,需要3个条件:3个隐藏字段,undo log以及readview。
(1)隐藏字段
表结构中除了行数据以外,还有3个隐藏字段:
-
DB_TRX_ID:最新修改的事务ID,记录插入和修改最后一次的事务ID
-
DB_ROLL_PTR :回滚指针,指向上一个版本记录
-
DB_ROW_ID:隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。
(2)undo log
回滚日志,在insert update delete语句执行时保存回滚的日志。
当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除。
而update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即 被删除。
不同事务对同一条记录进行修改后,会生成一条版本连,下图为undo log中记录的版本链。
(3)readview
ReadView(读视图)是 快照读 SQL执行时MVCC提取数据的依据。
readView包含四个核心字段:
-
m_ids:当前活跃的事务ID集合
-
min_trx_id:最小活跃事务ID
-
max_trx_id:预分配事务ID,当前最大事务ID+1(因为事务ID是自增的)
-
creator_trx_id:ReadView创建者的事务ID
在readview中就规定了版本链数据的访问规则:
-
trx_id == creator_trx_id:可以访问该版本 成立,说明数据是当前这个事 务更改的。
-
trx_id < min_trx_id :可以访问该版本 成立,说明数据已经提交了。
-
trx_id > max_trx_id:不可以访问该版本 成立,说明该事务是在 ReadView生成后才开启。
-
min_trx_id <= trx_id <= max_trx_id:如果trx_id不在m_ids中, 是可以访问该版本的 成立,说明数据已经提交。
不同的隔离级别,生成readview的时机不一样:
READ COMMITTED :在事务中每一次执行快照读时生成ReadView。
REPEATABLE READ:在事务第一次执行快照读的时候生成ReadView。
3、原理分析
(1)RC隔离分析
RC隔离级别是解决脏读。在事务提交之后才能读到修改后的记录。这个是怎么做到呢,以下图为例子,事务5在每次快照读的时候都会生成一个readview。
根据undo log版本链记录和readview的匹配规则,我们来分析以下,事务5第一个查询,获取的结果是哪一个呢?
按照上图中DB_TRX_ID为4,和事务5的第一次查询的readview进行对比,发现1,2,3,4都不匹配。
然后匹配下一条,DB_TRX_ID为3,和事务5的第一次查询的readview进行对比,发现1,2,3,4都不匹配。
然后匹配下一条,DB_TRX_ID为2,和事务5的第一次查询的readview进行对比,发现第二个是匹配的。那就说明此快照读,返回的是此版本链的数据。
事务5第二个查询,获取的结果是哪一个呢?
按照上图中DB_TRX_ID为4,和事务5的第二次查询的readview进行对比,发现1,2,3,4都不匹配。
然后匹配下一条,DB_TRX_ID为3,和事务5的第二次查询的readview进行对比,发现第二个是匹配的。那就说明此快照读,返回的是此版本链的数据。
(1)RR隔离分析
RR隔离级别下,仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。 而RR 是可 重复读,在一个事务中,执行两次相同的select语句,查询到的结果是一样的。以下图为例子,事务5在第一次快照读的时候会生成一个readview,后面就会服用此readview。上述已经分析过每次查询的记录在undo log中DB_TRX_ID为2的版本链是匹配的。
总结:
MVCC使用隐藏字段、undo log,readview实现的,没修改一次都会在undo log中生成一个版本记录,对于同一条记录会生成一个版本链,每条记录中包含隐藏字段。而readview则是在每次快照读的时候生成,通过版本链中的trx_id字段,对比readview中的记录,通过readview的规则,判断哪条版本记录是匹配的,从而得到最终结果。所以MVCC+锁实现了事务的隔离性。