MySql中MVCC多版本并发控制器执行机制详解

前言

  • 网上有很多文章都有对MySQL的版本并发控制器MVCC的介绍,这次结合个人理解记录一下,加深印象也方便以后查阅。

MySQL事务特性(ACID)

  • 原子性:一个事务内的所有操作要么全部执行,要么全部不执行。如果事务执行到中间过程时,出现异常,需要把之前已经执行的数据进行回滚操作
  • 一致性:一个事务开始前和结束后中,数据库中数据是具有完整性的。这表示更新的数据符合所有的预计规则
  • 持久性:一个事务内数据提交结束后,数据是永久性改变,服务器故障也不会丢失最新数据
  • 隔离性:多个事务如果并发执行读写操作,那么事务间操作的数据是隔离的、互不影响的,防止出现脏数据
    其中事务隔离又分为四个级别:读未提交、读已提交、可重复读、串行化

MySQL事务隔离级别

  • 读未提交(read uncommitted):一个事务内可以读取到其他事务已操作但未提交的数据,会出现脏读、不可重复读、幻读
  • 读已提交(read committed):一个事务内可以读取到其他事务已操作且已提交的数据,会出现不可重复读、幻读
  • 可重复读(repeatable read):一个事务内查询的数据保持一致,会出现幻读
  • 串行化(serializable):一个事务内在所影响的所有数据都加读锁和写锁,等事务最终提交完成后再释放锁,这能解决脏读、不可重复读、幻读,但效率最低

MySQL事务隔离级别所带来的问题

  • 脏读:一个事务内读取到其他事务未提交的数据,而其他事务内的数据可能会因为回滚操作,所以导致脏数据的产生
  • 不可重复读:一个事务内读取的数据被其他事务更新且提交了,导致当前事务再次读取时发现数据不一致
  • 幻读:一个事务内根据条件读取数据,这时其他事务新增或删除符合当前事务查询条件的数据且提交事务,导致当前事务再次根据条件查询发现结果条数和之前的不一致

注:不可重复读和幻读有点相似。不可重复读是对同一数据进行修改操作,幻读是新增或删除数据行。

MVCC版本并发控制器

  • MySQL中对事务并发读读情况,不需要加其他额外操作来保证数据的一致性。
  • MySQL中对事务并发写写情况,是直接加锁。一个事务更新了某条数据,就会给该数据加锁,其他事务想要更新同一条数据需要等当前事务提交解锁后才能更新。
  • MySQL中对事务并发读写情况,使用MVCC多版本并发控制器进行处理,其目的是为了提高数据库在高并发下的性能效率。

MVCC之版本链

  • 在MySQL数据库中使用innodb储存引擎,在表的聚集索引记录中会包含两个隐藏列:trx_id和roll_pointer
  • trx_id:在对某条数据进行更新时,会把对应的事务id赋值到当前隐藏字段trx_id
  • roll_pointer:在对某条数据进行更新时,当前隐藏列会用一个指针指向改动前的历史版本(是指向undo日志记录,如果是新增,当前隐藏列列为null)
  • MySQL在对数据进行更新操作(insert、update、delete)时,MySQL会记录redo日志(解决服务宕机重启数据丢失问题),binlog日志(用于数据备份,主从复制等),undo日志文件(用于事务提交或回滚操作)中,
  • 而每一条undo日志记录都对应着一个roll_pointer属性字段,把这些undo日志串联起来就是一个链表结构,这就是版本链

bbl.png
版本链的头节点是最新数据,每个节点都包含的有对应事务id

  • MySQL隔离级别如果使用RN(读未提交),那就直接使用版本链中最新记录访问数据
  • MySQL隔离级别如果使用串行化,那就通过加锁解锁的方式访问数据
  • MySQL隔离级别如果RC(读已提交)和RR(可重复读),那就需要通过版本链中不同版本进行访问记录,那么这里就需要知道该用哪种版本来的数据进行访问?

MVCC之ReadView

  • ReadView是一个存储活跃事务ID的列表,里面包含四个重要的属性:
  • m_ids:表示活跃事务id列表(开启事务,但还未提交)
  • creator_trx_id:表示当前事务id
  • min_trx_id:表示活跃事务列表中最小事务id
  • max_trx_id:表示当前最大事务id + 1(代表下次递增的事务id)
  • 当一个事务访问某行数据时,按照官方规则规定读取数据:
    • 1、被访问数据的事务id(trx_id)小于当前事务ReadView列表min_trx_id,表示在当前事务开启之前数据就已经被提交,可以访问
    • 2、被访问数据的事务id(trx_id)大于当前事务ReadView列表max_trx_id,表示在当前事务开启后数据才创建提交的,不能被访问
    • 3、被访问数据的事务id(trx_id)在当前事务ReadView列表的min_trx_id和max_trx_id之间,那就分为两种情况:
      • 3.1、被访问数据的事务id(trx_id)在当前事务ReadView列表m_ids(活跃列表)中,表示当前被访问数据是和当前事务同时间段创建,不能被访问
      • 3.2、被访问数据的事务id(trx_id)不在当前事务ReadView列表m_ids(活跃列表)中,表示当前被访问数据是在当前事务创建之前提交,可以访问

注:MySQL事务创建后会在第一个执行更新操作(insert、update、delette)时,才会创建一个事务id,后续更新操作继续使用当前事务id

  • RC(读已提交)和RR(可重复读)之间很大的一个区别点就是基于ReadView创建时机的不同:
    • RC(读已提交)隔离级别:每次读取数据前,都生成一个ReadView
    • RR(可重复读)隔离级别:在第一次读取数据前,生成一个ReadView

RC(读已提交)会出现不可重复读

  • 例如:表里有新增一条数据dataA,其事务id为10。然后创建事务A和事务B并发执行,其事务A的id为20,其事务B的id为30。
  • 这时事务A读取数据dataA会生成一个ReadView列表:m_ids【20,30】,creator_trx_id=20,min_trx_id=20,max_trx_id=31
  • 因为被读取的数据dataA事务id为10,小于当前事务min_trx_id,可以访问数据dataA。
  • 这时事务B读取数据dataA会生成一个ReadView列表:m_ids【20,30】,creator_trx_id=30,min_trx_id=20,max_trx_id=31
  • 这时dataA事务id为10,仍可以读取,然后事务B对dataA做更新操作,并提交事务。这时dataA事务id变为30
  • 这时事务A读取数据dataA会再次生成一个ReadView列表:m_ids【20】,creator_trx_id=20,min_trx_id=20,max_trx_id=31
  • 这时dataA事务id为30,这时属于上述规则3.2,可以访问dataA。
  • 这就是RC隔离级别产生不可重复读的机制

RR(可重复读)不会出现不可重复读

  • 例如:表里有新增一条数据dataA,其事务id为10。然后创建事务A和事务B并发执行,其事务A的id为20,其事务B的id为30。
  • 这时事务A读取数据dataA会生成一个ReadView列表:m_ids【20,30】,creator_trx_id=20,min_trx_id=20,max_trx_id=31
  • 因为被读取的数据dataA事务id为10,小于当前事务min_trx_id,可以访问数据dataA。
  • 这时事务B读取数据dataA会生成一个ReadView列表:m_ids【20,30】,creator_trx_id=30,min_trx_id=20,max_trx_id=31
  • 这时dataA事务id为10,仍可以读取,然后事务B对dataA做更新操作,并提交事务。这时dataA事务id变为30
  • 这时事务A读取数据dataA会使用第一次的ReadView列表:m_ids【20,30】,creator_trx_id=20,min_trx_id=20,max_trx_id=31
  • 这时dataA事务id为30,这时属于上述规则3.1,不可以访问。
  • 这时会根据dataA事务id为30所在行的roll_pointer字段,找到指向上一个版本中dataA事务id为10的数据。这个数据是可以访问。
  • 这就是RR隔离级别解决不可重复读的机制

最后

  • 虚心学习,共同进步 -_-
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值