【2. MVCC-多版本并发控制技术】

快照读与当前读

快照读(普通select语句)通过MVCC方式解决幻读

  • MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理 读-写冲突 ,做到即使有读写冲突时,也能做到 不加锁 , 非阻塞并发读 ,而这个读指的就是 快照读(属于乐观锁) , 而非 当前读 。

当前读(select...for update等语句)通过next-key lock(记录锁+间隙锁)

  • 当前读实际上是一种加锁的操作,是悲观锁的实现。而MVCC本质是采用乐观锁思想的一种方式。
  • 当前读读取的是记录的最新版本(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁

隔离级别

MySQL通过MVCC解决幻读,MVCC多版本并发控制结束,多版本通过undolog实现,管理通过ReadView实现

  • 事务有四个隔离级别,可能存在三种并发问题:

在这里插入图片描述

  • 但是在MySQL中,默认的隔离级别是可重复读,可以解决脏读和不可重复读,如果从定义来看,它不能解决幻读,只能采取可串行化
  • 但是MVCC可以不采用锁机制,而是通过乐观锁方式解决幻读,所以MySQL在可重复读也解决了幻读问题。
    在这里插入图片描述

这四种隔离级别具体是如何实现的呢?

  • 对于「读未提交」隔离级别的事务来说,因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了;
  • 对于「串行化」隔离级别的事务来说,通过加读写锁的方式来避免并行访问;
  • 对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同,大家可以把 Read View 理解成一个数据快照,就像相机拍照那样,定格某一时刻的风景。「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,而「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View。

注意,执行「开始事务」命令,并不意味着启动了事务。在 MySQL 有两种开启事务的命令,分别是:

  • 第一种:begin/start transaction 命令;
  • 第二种:start transaction with consistent snapshot 命令;

这两种开启事务的命令,事务的启动时机是不同的:

  • 执行了 begin/start transaction 命令后,并不代表事务启动了。只有在执行这个命令后,执行了增删查改操作的 SQL 语句,才是事务真正启动的时机;
  • 执行了 start transaction with consistent snapshot 命令,就会马上启动事务。

总结:

  • MVCC读的是快照,也就是乐观锁的实现方式,幻读不会出现,相当于解决了幻读,但是并不是编程串行化了,串行化读是通过加锁,一个一个的进行解决。

隐藏字段、Undo log版本链

  • 针对每一条记录(行格式),有三个隐藏字段
    • UUID:如果没有主键也没有唯一性索引,默认提供一个UUID隐藏字段
    • trx_id:记录最近一个更新事务的ID(当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里)
    • roll_pointer:回滚指针,是通过和undo log日志,旧版本写入到undo日志中,旧版本通过链表指针相连接,roll_point指向最近的undo log记录
      在这里插入图片描述

ReadView

  • 使用 READ COMMITTEDREPEATABLE READ 隔离级别的事务,都必须保证读到 已经提交了的 事务修改过的记录。假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的
  • 核心问题就是需要判断一下版本链中的哪个版本是当前事务可见的,这是ReadView要解决的主要问题
  1. creator_trx_id ,创建这个 Read View 的事务 ID。
  2. m_ids ,表示在生成ReadView时当前系统中「活跃且未提交」的 事务id列表 (“活跃事务”指的就是,启动了但还没提交的事务。)
  3. min_trx_id ,创建 Read View 时,当前数据库中「活跃事务且未提交的」事务中最小事务的事务 id
  4. max_trx_id ,表示生成ReadView时系统中应该分配给下一个事务的 id 值。max_trx_id 是系统最大的事务id值,这里要注意是系统中的事务id,需要区别于正在活跃的事务ID。
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

MVCC整体操作流程/如何解决幻读(重点)

  1. 首先获取事务自己的版本号,也就是事务 ID;
  2. 获取 ReadView;(在第一个查询语句后,会创建一个Read View后续的查询语句利用这个 Read View)
  3. 查询得到的数据,然后与 ReadView 中的事务版本号进行比较;
  4. 如果不符合 ReadView 规则,就需要从 Undo Log 中获取历史快照;
  5. 最后返回符合规则的数据。

总结

  1. MVCC 在 READ COMMITTDREPEATABLE READ 这俩个隔离级别才有效,因为这俩个情况下考虑快照(读旧数据),而剩下的读为提交和串行化,读的都是最新数据
  2. 核心点在于 ReadView 的原理, READ COMMITTDREPEATABLE READ 这两个隔离级别的一个很大不同
    就是生成ReadView的时机不同:
    • READ COMMITTD 在每一次进行普通SELECT操作前都会生成一个ReadView
    • REPEATABLE READ 只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复
      使用这个ReadView就好了。

在这里插入图片描述

当前读是如何避免幻读的?

  • MySQL 里除了普通查询是快照读,其他都是当前读,比如 update、insert、delete,这些语句执行前都会查询最新版本的数据,然后再做进一步的操作。
  • 假设你要 update 一个记录,另一个事务已经 delete 这条记录并且提交事务了,这样不是会产生冲突吗,所以 update 的时候肯定要知道最新的数据。
  • 使用间隙锁,对范围加锁使得插入或者删除或者更新无法操作

在这里插入图片描述

幻读没有被完全解决?

场景一:

  • 在可重复读隔离级别下,事务 A 第一次执行普通的 select 语句时生成了一个 ReadView,之后事务 B 向表中新插入了一条 id = 5 的记录并提交。接着,事务 A 对 id = 5 这条记录进行了更新操作,在这个时刻,这条新记录的 trx_id 隐藏列的值就变成了事务 A 的事务 id,之后事务 A 再使用普通 select 语句去查询这条记录时就可以看到这条记录了,于是就发生了幻读。

场景二:

  • T1 时刻:事务 A 先执行「快照读语句」:select * from t_test where id > 100 得到了 3 条记录。
  • T2 时刻:事务 B 往插入一个 id= 200 的记录并提交;
  • T3 时刻:事务 A 再执行「当前读语句」 select * from t_test where id > 100 for update 就会得到 4 条记录,此时也发生了幻读现象。

要避免这类特殊场景下发生幻读的现象的话,就是尽量在开启事务之后,马上执行 select … for update 这类当前读的语句,因为它会对记录加 next-key lock,从而避免其他事务插入一条新记录。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小呆鸟_coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值