MySQL MVCC

总结自小林codingbojiangzhou

脏读、不可重复读、幻读 说的都是并发读取的问题,最简单的方式就是给记录加一把锁,不管是更新、读取记录都需要竞争到这把锁之后才能操作。但这种方式的并发性能可想而知会有多么低。

于是 InnoDB 就设计了MVCC来解决并发读取的问题,MVCC 就是多版本并发控制(Multi-Version Concurrency Control)。在 RCRR 这两种隔离级别下执行SELECT查询时,通过访问记录的版本链,而不需要加锁,这样使得不同事务的读-写操作可以并发执行,从而提升数据库的性能。

MVCC需要依赖undo log版本链:

  • 对于使用 RU 隔离级别的事务来说,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好了。

  • 对于使用 RCRR 隔离级别的事务来说,都必须保证读到已提交事务修改过的记录,如果另一个事务修改的记录还未提交,是不能直接读取记录的最新版本的,此时就可以沿着undo版本链查找当前事务可见的版本。

ReadView

那如何判断版本链上的哪个版本是当前事务可见的呢?

InnoDB 设计了一个 ReadView,在执行一个事务的时候就会创建一个ReadView。ReadView 有四个关键属性:

  • m_ids :指的是在创建 Read View 时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表,"活跃事务"指的就是,启动了但还没提交的事务。

  • min_trx_id :指的是在创建 Read View 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。

  • max_trx_id :这个并不是 m_ids 的最大值,而是创建 Read View 时当前数据库中应该给下一个事务的 id 值,也就是全局事务ID(Max Trx Id);

  • creator_trx_id :指的是创建该 Read View 的事务的事务 id。事务中只有在执行了增删改操作时才会分配一个事务ID,如果是一个只读事务,那 creator_trx_id 默认就为0

MVCC 流程

undo log 中的隐藏列 trx_id 表示产生这条 undo log 时的事务的事务ID。判断此版本是否可访问的依据就是用 undo log 中的 trx_id 属性值与 ReadView 中的各个属性做比较。

通过如下步骤来判断版本是否可被访问:

  • ① 如果 trx_id 等于 creator_trx_id ,说明当前事务在访问它自己修改过的记录,所以该版本记录可以被当前事务访问。(可以自己访问自己的事务)

  • ② 如果 trx_id 小于 min_trx_id,说明生成该版本记录的事务在当前事务生成 ReadView 前已经提交,所以该版本记录可以被当前事务访问。(可以访问已经提交的事务)

  • ③ 如果 trx_id 大于或等于max_trx_id,说明生成该版本记录的事务在当前事务生成 ReadView 后才开启,所以该版本记录不可以被当前事务访问。(不能访问"未来"的事务)

  • ④ 如果 trx_idmin_trx_idmax_trx_id 之间,此时再判断一下 trx_id 是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本记录的事务还是活跃的,该版本记录不可以被访问(不能访问同期未提交的事务);如果不在,说明创建 ReadView 时生成该版本记录的事务已经被提交,该版本记录可以被访问。(可以访问同期已提交的事务)

RC 和 RR

READ COMMITTEDREPEATABLE READ 隔离级别的区别就是它们生成ReadView的时机不同。

  • READ COMMITTED 是每次查询前都会生成一个独立的 ReadView。

  • REPEATABLE READ 则只在第一次查询前生成一个 ReadView,之后的查询都重复使用这个 ReadView。

  • READ UNCOMMITTED 则不需要生成 ReadView,直接读取行记录的数据。

快照读和当前读

简单的SELECT查询,是读取undo版本链上的一个快照版本,可以称为快照读一致性非锁定读。由于是读取的快照,因此在RR隔离级别下可以避免幻读的发生。

但如果是INSERT、DELETE、UPDATE语句,例如下面的SQL,这个 UPDATE 语句会更新 balance=0 的记录,这种方式就称为当前读,读取的是最新的数据。当前读能读取到别的事务已提交的修改,就可能会产生幻读的问题。UPDATE account SET balance=100 WHERE balance = 0;

而对于幻读现象,不建议将隔离级别升级为串行化,因为这会导致数据库并发时性能很差。MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象,解决的方案有两种:

  • 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。

  • 针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。

相关推荐
q90854470311 分钟前
MySQL 二进制日志binlog解析
mysql·binlog·binlog2sql·my2sql
重庆小透明25 分钟前
力扣刷题记录【1】146.LRU缓存
java·后端·学习·算法·leetcode·缓存
lang2015092831 分钟前
Reactor操作符的共享与复用
java
TTc_41 分钟前
@Transactional事务注解的批量回滚机制
java·事务
博观而约取1 小时前
Django 数据迁移全解析:makemigrations & migrate 常见错误与解决方案
后端·python·django
Edingbrugh.南空1 小时前
Flink SQLServer CDC 环境配置与验证
数据库·sqlserver·flink
clock的时钟1 小时前
暑期数据结构第一天
数据结构·算法
码不停蹄的玄黓1 小时前
MySQL分布式ID冲突详解:场景、原因与解决方案
数据库·分布式·mysql·id冲突
wei_shuo2 小时前
飞算 JavaAI 开发助手:深度学习驱动下的 Java 全链路智能开发新范式
java·开发语言·飞算javaai
小小小小王王王2 小时前
求猪肉价格最大值
数据结构·c++·算法