深入理解mysql的MVCC机制及实例详解

一、MVCC多版本并发控制机制

  • mysql在读已提交可重复读的隔离级别下,通过MVCC机制实现的。

二、undo日志版本链

  • undo日志版本链是指一行数据被多个事务依次修改过后,在每个事务修改完后,Mysql会保留修改前的数据undo回滚日志,并且用两个隐藏字段trx_id和roll_pointer把这些undo日志串联起来形成一个历史记录版本链

三、read view机制详解

  • 当事务开启,执行任何查询sql时会生成当前事务的一致性视图read-view,该视图在事务结束
    之前都不会变化(如果是读已提交隔离级别在每次执行查询sql时都会重新生成),这个视图由执行查询时所有未提交事务id数组(数组里最小的id为min_id)和已创建的最大事务id(max_id)组成,事务里的任何sql查询结果需要从对应版本链里的最新数据开始逐条跟read-view做比对从而得到最终的快照结果。

四、实例

假如数据表test中有一条id为1的数据,name值为1,是由事务1插入完成的。然后开启如下事务:
开启事务如下100、200、300,依次按照从上往下的时间执行:

时间# Transaction 100# Transaction 200# Transaction 300# select 1
1begin;begin;begin;begin;
2update test set name = ‘2_100’ where id =3;
3update test set name = ‘3_200’ where id =2;
4update test set name = ‘4_300’ where id = 1;
5commit;
6select name from test where id = 1;
7update test set name = ‘9_100’ where id = 1;
8select name from test where id = 1;
9commit;
10update test set name = ‘12_300’ where id = 1;
11select name from test where id = 1;
12commit;

每一条数据都有一个undo日志版本链,这里只研究id为1的:

  1. 时间1时,都开启事务
  2. 时间2:Transaction 100事务,将id为3的记录name值修改为2_100
  3. 时间3:Transaction 200事务,将id为2的记录修改为3_200
  4. 时间4:Transaction 300事务,将id为1的记录修改为4_300,原本表中的值为1。(id,name,trx_id,roll_pointer)其中id代表记录ID,name就是这次修改的字段,trx_id则是事务id,roll_pointer指向下一个修改记录。则当前生成的undo日志版本链,可大致表示为:(1,1,1,)–>(1,4_300,300,)
  5. 时间5:提交Transaction 300事务。
  6. 时间6:# select 1对id为1的记录进行查询。根据上面read-view生成规则(所有未提交事务id数组和已创建的最大事务id组成)。此时未提交的事务为Transaction 100、Transaction 200,组成的数组可记为[100,200],已创建的最大事务id为300.则当前read-view为[100,200],300
  7. 时间7:Transaction 100事务,将id为1的记录name值修改为9_100,则undo日志版本链变为(1,1,1,)–> (1,4_300,300,) -->(1,9_100,100,)
  8. 时间8:# select 1对id为1的记录进行查询。则当前的read-view为[100,200],300
  9. 时间9:提交Transaction 100事务。
  10. 时间10: Transaction 200事务,将id为1的记录name值修改为12_300,则undo日志版本链变为(1,1,1,)–> (1,4_300,300,) -->(1,9_100,100,)–>(1,12_300,200,)
  11. 时间11:# select 1对id为1的记录进行查询。则当前的read-view为[200],300
  12. 时间12:提交Transaction 200事务。

版本链比对规则:

  1. 如果 row 的 trx_id 落在( trx_id<min_id )部分,表示这个版本是已提交的事务生成的,这个数据是可见的;
  2. 如果 row 的 trx_id 落在( trx_id>max_id )部分,表示这个版本是由将来启动的事务生成的,是不可见的(若row 的 trx_id 就是当前自己的事务是可见的);
  3. 如果 row 的 trx_id 落在部分(min_id <=trx_id<= max_id),那就包括两种情况
    a. 若 row 的 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见(若 row 的 trx_id 就是当前自己的事务是可见的);
    b. 若 row 的 trx_id 不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。

1.当开启读已提交隔离级别

  • 时间6:read-view为[100,200],300,其undo日志链为(1,1,1,)–>(1,4_300,300,);当前undo日志记录的事务id为300,满足上面版本链对比规则第三条中的b,则代表可见。所以此刻查询出来的值为4_300
  • 时间8:read-view为[100,200],300,其undo日志链为(1,1,1,)–> (1,4_300,300,) -->(1,9_100,100,);当前的事务id为100,满足上面版本链对比规则第三条中的a,则代表不可见。此时就要回退一个版本链到 (1,4_300,300,),上步已做分析可见。所以此刻查询出来的值为4_300
  • 时间11:read-view为[200],300,其undo日志链为(1,1,1,)–> (1,4_300,300,) -->(1,9_100,100,)–>(1,12_300,200,),满足上面版本链对比规则第三条中的a,则代表不可见。此时就要回退一个版本链到 (1,9_100,100,),满足上面版本对比规则第一条,代表可见。则读取的数据为9_100

2.当开启可重复读隔离级别

开启可重复读,与读已提交的对比规则一致。但是,在可重复读的隔离级别下,执行第一条查询语句生成read-view后,后面不再改变。
如上面实例:

  • 在时间6:生成read-view为[100,200],300后,时间8和时间11的read-view仍然为[100,200],300。
  • 剩下的比对规则跟上面一致。如此也就保证可重复读。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值