谈谈mysql中的MVCC

在说MVCC之前我们还是来复习一下事务的隔离级别和并发情况下出现的问题

事务的隔离级别:

read uncommitted:读未提交 最弱的一种隔离级别,最容易出现脏读
read committed:读已提交
repeatable read:可重复读
serializable:串行化
每一种隔离级别对应出现的并发问题如下

MVCC

版本链

对于innoDB存储引擎来说,他的聚簇索引每一行都包含着两个隐藏的默认列,分别是:

trx_id:记录修改当前索引对应的数据的事务的ID

roll_pointer :每当数据更新时,都会把旧版本的数据写到undo日志中,roll_pointer就相当于一个指针,指向上一条最新更新的记录

开启一个事务,假设事务id是80,数据库表中存在一条数据

此时我们开启另一个事务id为100的session要对这条数据进行更新:

begin
UPDATE hero SET name='小红' WHERE id=2

UPDATE hero SET name='小李' WHERE id=2

##没有commit提交
复制代码

那么此时版本链的内容如下

所有的版本都会被roll_pointer连接成一个链表,头节点就是当前记录的最新的值,另外,每一行中还有当前事务的id

readview

mvcc只在rc(read committed)和rr(repeatable read)中使用到,是因为read uncommitted会造成脏读,那读取最新的表头数据(小李)就可以,serializable会锁定整张表,也用不到。我们都知道rc和rr都会读到已经提交的数据,那么它们是如何选择要读取版本链中的某一条数据的呢?我们这里先介绍一个readview概念。 当我们写一条select语句的时候,会自动生成一个readview视图,视图中包含以下信息

m_ids:还没有提交的事务的id列表
min_m_ids:m_ids中的最小值
max_trx_id:已经创建的id最大的事务的id(包含已经提交了的)
create_trx_id:生成当前readview的事务的id

我们主要记住m_ids和max_trx_id,并且记住下图

当一条select语句执行时候,它会根据版本链去依次查看当前版本的事务id(trx_id),然后放到readview视图中做比较

版本链比较:

如果落在绿色区域,这个数据是可见的
如果落在红色区域,这个数据是不可见的
如果落在黄色区域:
1,如果当前版本的事务id是未提交的,就不可见
2,如果当前版本的事务id是已经提交的,可见
复制代码

对于RC和RR,生成readview的方式和时间也不同,我们这里分别举一个例子

repeatable read ——在第一次读取数据的时候生成一个readview

在同一个事务中,只在第一个select语句中生成一个readview,之后就不再生成了。
假设我们这里有2个事务,事务A,id100.事务B,id200

事务A执行两次更新语句

begin
UPDATE hero SET name = '吉吉' WHERE number = 2;
UPDATE hero SET name = ‘吉吉2' WHERE number = 2;
复制代码

事务B

begin
复制代码

此时版本链中数据如下

当一条查询语句执行时候,假设事务id为300

SELECT * FROM hero WHERE number = 2 ##查询到的结果是小明
复制代码

这条select语句的执行过程如下
先生成readview,【】内代表还没有提交的事务,不可见

然后从版本链第一条开始查找,第一个吉吉2对应的事务id为100,在黄色区域,并且是【】内,不可见。以此向下寻找,直到找到事务id为80的小明,落在绿色区域,可见。

然后我们把事务A提交

begin
UPDATE hero SET name = '吉吉' WHERE number = 2;
UPDATE hero SET name = ‘吉吉2' WHERE number = 2;
commit;
复制代码

事务B,id为200开始更新

begin
UPDATE hero SET name = '吉吉3' WHERE number = 2;
UPDATE hero SET name = ‘吉吉4' WHERE number = 2;
commit;
复制代码

此时的版本链如图

在刚刚开启查询的事务中再次执行查询语句

SELECT * FROM hero WHERE number = 2 ##查询到的结果还是小明
复制代码

这是因为在RR隔离级别下,每一个查询事务只会生成第一条select语句中的readview,所以此时的版本链还是和第一条select语句一样

从版本链第一个节点开始找,第一个id为200,在【】内,属于没有提交,所以不可见,以此往下,知道最后的80,在绿色区域内,可见。

read committed ——每次select都会生成一个readview

第一次查询select结果和RR的一样,我们从RR事务B更新开始说起。

SELECT * FROM hero WHERE number = 2 ##查询到的结果是吉吉2
复制代码

此时的版本链如图

当执行第二遍select的时候,会重新生成readview表格,此时事务A,id100已经提交,所以readview如下

根据版本链头节点开始查找,第一个id为200,在黄色区域,没有提交,所以不可见,以此类推,到达吉吉2的id为100的时候,在绿色区域,可见。

MVCC总结:

在使用 read commitd和repeatable read隔离级别执行查询的时候,根据不同事务的读写,和写读来提高效率

read committed和repeatable read最大的不同是在生成readview的时机不同,repeatable read在同一个事务下只在第一次select中生成readview,所以不会出现不可重复读。而read committed在每一个查询都会生成readview,所以会出现不可重复读。

参考:

MySQL 是怎样运行的:从根儿上理解 MySQL

作者:小孩子4919

猜你喜欢

转载自juejin.im/post/5eea0ef7f265da02a0097916