Featured image of post MySQL - MVCC

MySQL - MVCC

MVCC:Multi Version Concurrency Control,多版本并发控制,是 MySQL InnoDB 引擎用于控制数据并发访问的协议

为什么需要?

我们知道 InnoDB 中有很多种锁,而锁本身就是用于并发控制的,那为什么还要引入 MVCC 呢?

相对来说,锁的性能并不好。而对于数据库应用来说,读操作的频率远高于写操作,因此需要尽可能的保证读操作不会被写操作阻塞。

因此引入了 MVCC 来解决这个问题。MVCC 允许读操作不加锁,直接读取数据。

隔离级别

  • 读未提交:Read UnCommitted 是指一个事务可以看到其他事务未提交的数据
  • 读已提交:Read Committed 是指一个事务只能看到其他事务已经提交的数据
  • 可重复读:Repeatable Read 是指一个事务在执行过程中,读取到的数据是事务开始时的快照数据。既在同一个事务内部多次读取同一数据,读到的结果都相同,即使这个数据已经由其他事务修改过了
  • 串行化:Serializable 是指事务对数据的读写都是串行的

从上到下,隔离性一次变强,但性能也变差了

与此相关的是三个读异常:

  • 脏读:一个事务读取到另一个事务未提交的数据。之所以称为脏读,是因为未提交的数据可能会被回滚
  • 不可重复读:一个事务执行过程中,对同一行数据读到的结果不同
  • 幻读:事务执行过程中,别的事务插入了新的数据并且提交了,然后事务在后续步骤中读到了这个数据

而对于这三个异常:

  • 读未提交:三个异常均可能出现
  • 读已提交:脏读不可能出现,幻读和不可重复读可能出现
  • 可重复读:脏读和不可重复读不可能出现,幻读理论上可能出现
  • 串行化:三个异常均不可能出现

理论上可重复读级别并没有完全解决幻读问题。但是 MySQL 通过使用临键锁来解决了幻读问题,因此可视为可重复读级别下幻读不可能出现

此外还有两个概念:

  • 快照读:在事务开始时创建一个数据的快照,整个事务过程都使用这个快照来读取数据(基于 MVCC 实现)
  • 当前读:每次都读取最新数据

版本链

为了实现 MVCC,InnoDB 给每一行数据都加了两个额外字段 trx_id 和 roll_ptr:

  • trx_id:事务 id,也叫事务版本号。MVCC 中的 V 既为这个字段。每个事务开始时都会获得一个 id,然后事务内操作的行的 trx_id 都会被设置为这个 id
  • roll_ptr:回滚指针,InnoDB 通过 roll_ptr 指向 undo log 中的记录,来把每一行的历史版本串联起来

那么假设我有一个事务 C,需要读取 x 的值,那么应该读取哪个版本呢?这这几道另一个概念 ReadView

ReadView

ReadView 可以理解为一种可见性规则,在 undolog 中存放着历史版本的数据,当事务需要读取数据的时候,Read View 就会用来控制这个事务应该读取哪个版本的数据

Read View 只用于读提交和可重复读两个级别:

  • 读已提交:事务每次发起查询的时候,都会重新创建一个新的 Read View。也就意味着在整个事务中,Read View 是在不断变动的
  • 可重复读:事务在开始时创建一个 Read View,整个事务过程都使用这个 Read View

一些问题

为什么有些公司会选择使用读提交级别而不是默认的可重复读?

首先需要明确两者的区别,隔离级别可以视为为了解决三个读异常而提出的,在这方面两者的区别:

  • 读已提交:可能会出现幻读和不可重复读
  • 可重复读:可能会出现幻读(当然实际上通过临键锁解决了,因此可以视为不会出现)

而对于这两个问题来说:

  • 幻读:在大多数情况下,幻读不会影响业务的正确性。例如我正在执行事务 A,中途事务 B 修改了一个数据提交了,那么之后我再事务 A 中读取到了这条修改后的数据。从业务上讲,这是正确的,并没有什么问题
  • 不可重复读:意味着需要在同一个事务中多次读取同一行数据,通常可以在业务代码中进行优化
Licensed under CC BY-NC-SA 4.0
最后更新于 Nov 07, 2020 00:00 UTC