当前位置: 首页 > article >正文

【MySQL】MVCC及其实现原理

目录

1. 概念介绍

什么是MVCC

什么是当前读和快照读

MVCC的好处

2. MVCC实现原理

隐藏字段

Read View

undo-log

数据可见性算法

3. RC和RR隔离级别下MVCC的差异

4. MVCC+Next-key-Lock 防止幻读


1. 概念介绍

什么是MVCC

Multi-Version Concurrency Control 多版本并发控制,MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问;在编程语言中实现事务内存。

                                                                                                         MVCC_百度百科

MVCC允许多个事务同时读取同一行数据,而不会彼此阻塞,每个事务看到的数据版本是该事务开始时的数据版本。这意味着,如果其他事务在此期间修改了数据,正在运行的事务仍然看到的是它开始时的数据状态,从而实现了非阻塞读操作。


什么是当前读和快照读

先来介绍一下MySQL下InnoDB索引的当前读和快照读:

  • 当前读(锁定读)

像 select lock in share mode (共享锁), select for update; update; insert; delete (排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁

  • 快照读(一致性非锁定读)

不加锁的 select 操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即 MVCC ,可以认为 MVCC 是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本

当前读是指读取数据时获取最新版本,并通过加锁确保其他事务无法修改该记录,以保证一致性。
快照读则是在不加锁的情况下读取数据,基于MVCC,可以访问历史版本,从而提高并发性能,但可能不获取最新数据。


MVCC的好处

  1. 提高并发性:通过允许多个事务同时访问不同版本的数据,减少了锁的竞争,从而提高了并发性能。

  2. 降低阻塞:读操作不需要等待写操作完成,减少了事务之间的阻塞,提升了响应速度。

  3. 保证一致性:每个事务都可以读取到一个一致的数据快照,避免了因并发更新导致的数据不一致问题。

  4. 性能优化:在许多情况下,MVCC可以避免加锁操作,降低了锁管理的开销,提升了数据库的整体性能。

  5. 灵活的事务处理:支持不同的隔离级别,可以根据需求灵活配置,以平衡性能和一致性。

2. MVCC实现原理

MVCC 的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 隐式字段undo日志 Read View 来实现的。所以我们先来看看它们的概念

隐藏字段

在内部,InnoDB 存储引擎为每行数据添加了三个 隐藏字段:

  • DB_TRX_ID(6字节):表示最后一次插入或更新该行的事务 id。此外,delete 操作在内部被视为更新,只不过会在记录头 Record header 中的 deleted_flag 字段将其标记为已删除
  • DB_ROLL_PTR(7字节) 回滚指针,指向该行的 undo log 。如果该行未被更新,则为空
  • DB_ROW_ID(6字节):如果没有设置主键且该表没有唯一非空索引时,InnoDB 会使用该 id 来生成聚簇索引

Read View

Read View 主要是用来做可见性判断,里面保存了 “当前对本事务不可见的其他活跃事务”:

主要有以下字段:

  • creator_trx_id:创建这个Read View的事务ID。这个ID标识了创建Read View的事务,而不是记录中的trx_id。如果是只读事务,事务ID默认为0。
  • m_ids:表示创建Read View时当前系统中活跃的读写事务的事务ID列表。这些是还未提交的事务,即启动了但还没提交的事务。

  • m_up_limit_id:表示创建Read View时活跃的事务中最小的事务ID。

  • m_low_limit_id:表示创建Read View时系统中应该分配给下一个事务的ID值,也就是当前最大事务ID+1。

class ReadView {
  /* ... */
private:
  trx_id_t m_low_limit_id;      /* 大于等于这个 ID 的事务均不可见 */

  trx_id_t m_up_limit_id;       /* 小于这个 ID 的事务均可见 */

  trx_id_t m_creator_trx_id;    /* 创建该 Read View 的事务ID */

  trx_id_t m_low_limit_no;      /* 事务 Number, 小于该 Number 的 Undo Logs 均可以被 Purge */

  ids_t m_ids;                  /* 创建 Read View 时的活跃事务列表 */

  m_closed;                     /* 标记 Read View 是否 close */
}

Read View 的作用主要是用来做可见性判断。当一个事务执行快照读操作时,它会使用Read View来判断自己能够看到哪个版本的数据。具体来说,系统会根据以下规则来判断记录的可见性:

不同的事务隔离级别下,Read View的生成时机也不同。例如,在READ COMMITTED隔离级别下,每次执行SELECT操作时都会生成一个新的Read View;而在REPEATABLE READ隔离级别下,只有在事务的第一次SELECT操作时会生成一个Read View,之后的查询都会复用这个Read View

  • 如果记录的trx_id等于creator_trx_id,说明这条记录是由当前事务自己修改的,因此对该事务是可见的。
  • 如果记录的trx_id小于m_up_limit_id,说明这个版本的记录是在创建Read View之前已经提交的事务生成的,所以该版本的记录对当前事务是可见的。
  • 如果记录的trx_id大于或等于m_low_limit_id,说明这个版本的记录是在创建Read View之后才启动的事务生成的,所以该版本的记录对当前事务是不可见的。
  • 如果记录的trx_id在m_up_limit_id和m_low_limit_id之间,就需要检查trx_id是否在m_ids列表中。如果在列表中,表示这个事务在Read View生成时刻还在活跃,还没有提交,因此该版本的记录对当前事务是不可见的;如果不在列表中,则说明这个事务在Read View生成之前就已经提交了,因此该版本的记录对当前事务是可见的。

事务可见性示意图


undo-log

undo log 主要有两个作用:

  • 当事务回滚时用于将数据恢复到修改前的样子
  • 另一个作用是 MVCC ,当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过 undo log 读取之前的版本数据,以此实现非锁定读(快照读)

InnoDB 存储引擎中 undo log 分为两种:insert undo logupdate undo log

  1. insert undo log:指在 insert 操作中产生的 undo log。因为 insert 操作的记录只对事务本身可见,对其他事务不可见,故该 undo log 可以在事务提交后直接删除。不需要进行 purge 操作。insert 时的数据初始状态:
  2. update undo logupdatedelete 操作中产生的 undo log。该 undo log可能需要提供 MVCC 机制,因此不能在事务提交时就进行删除。提交时放入 undo log 链表,等待 purge线程 进行最后的删除

数据第一次被修改时:

数据第二次被修改时:

不同事务或者相同事务的对同一记录行的修改,会使该记录行的 undo log 成为一条链表,链首就是最新的记录,链尾就是最早的旧记录。


数据可见性算法

InnoDB 存储引擎中,创建一个新事务后,执行每个 select 语句前,都会创建一个快照(Read View),快照中保存了当前数据库系统中正处于活跃(没有 commit)的事务的 ID 号。其实简单的说保存的是系统中当前不应该被本事务看到的其他事务 ID 列表(即 m_ids)。当用户在这个事务中要读取某个记录行的时候,InnoDB 会将该记录行的 DB_TRX_IDRead View 中的一些变量及当前事务 ID 进行比较,判断是否满足可见性条件。

1. 如果记录 DB_TRX_ID<m_up_limit_id,那么表明最新修改该行的事务(DB_TRX ID)在当前事务创建快照之前就提交了,所以该记录行的值对当前事务是可见的
2. 如果 DB TRX ID >= m_low_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建2快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤5
3.m_ids 为空,则表明在当前事务创建快照之前,修改该行的事务就已经提交了,所以该记录行的值对当前事务是可见的
4. 如果 m_up_limit id <= DB TRX ID<m_low_limit_id,表明最新修改该行的事务(DB TRX ID)在4.当前事务创建快照的时候可能处于“活动态”或者“已提交状态”;所以就要对活跃事务列表 m iàs 进行查找(源码中是用的二分查找,因为是有序的)

  • 如果在活跃事务列表 m ids 中能找到 DB TRX ID,表明:① 在当前事务创建快照前,该记录行的值被事务 ID 为 DB TRX ID 的事务修改了,但没有提交;或者 ② 在当前事务创建快照后,该记录行的值被事务 ID 为 DB TRX ID 的事务修改了。这些情况下,这个记录行的值对当前事务都是不可见的。跳到步骤 5
  • 在活跃事务列表中找不到,则表明“id 为 trx_id 的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见

5. 在该记录行的 DB_ROLL_PTR 指针所指向的 undo 1og取出快照记录,用快照记录的DB_TRX_ID 跳到步骤1重新开始判断,直到找到满足的快照版本或返回空。

3. RC和RR隔离级别下MVCC的差异

在MySQL的InnoDB存储引擎中,MVCC(多版本并发控制)是实现事务隔离级别的重要机制。MVCC通过保存数据的多个版本来允许读操作和写操作并发执行,从而提高数据库的并发性能。在RC(Read Committed)和RR(Repeatable Read)这两种隔离级别下,MVCC的工作方式有所不同。

RC隔离级别下的MVCC:

  • 在RC隔离级别下,每次执行快照读(SELECT)操作时,都会生成一个新的Read View。这意味着每次SELECT操作都可能看到不同的数据行版本,因为它们是基于各自时刻的Read View来判断数据的可见性。
  • Read View中包含了当前活跃的事务ID列表,以及这些事务的最小和最大事务ID。
  • 当读取一行数据时,会根据Read View来判断该数据行的版本是否对当前事务可见。

RR隔离级别下的MVCC:

  • 在RR隔离级别下,只有在事务的第一个快照读操作时会生成一个Read View,之后的快照读操作都会复用这个Read View。这样,同一个事务中多次执行SELECT操作,看到的都是事务开始时的数据行版本,从而保证了可重复读。
  • 这就意味着在RR隔离级别下,一个事务看不到其他事务在其第一个快照读之后对数据所做的修改。

4. MVCC+Next-key-Lock 防止幻读

InnoDB 存储引擎在 RR级别下通过 vcc和 Next-key Lock 来解决幻读问题。(在RC隔离级别下,由于每次SELECT操作都可能读取到最新的数据版本,因此不能避免幻读。)

1. 执行普通 select,此时会以 MVCC 快照读的方式读取数据

在快照读的情况下,RR 隔离级别只会在事务开启后的第一次查询生成 Read View ,并使用至事务提交。所以在生成 Read View 之后其它事务所做的更新、插入记录版本对当前事务并不可见,实现了可重复读和防止快照读下的 “幻读”

2、执行 select...for update/lock in share mode、insert、update、delete 等当前读

在当前读下,读取的都是最新的数据,如果其它事务有插入新的记录,并且刚好在当前事务查询范围内,就会产生幻读!InnoDB 使用 Next-key Lock 来防止这种情况。当执行当前读时,会锁定读取到的记录的同时,锁定它们的间隙,防止其它事务在查询范围内插入数据。只要我不让你插入,就不会发生幻读

这种锁定机制(Next-Key Lock)有效地解决了幻读问题,幻读是指事务在前后两次查询同一个范围的时候,第二次查询看到了第一次查询没有看到的行。由于Next-Key Lock的存在,当事务A在读取某个范围内的记录时,事务B想要在这个范围内插入一条新的记录将会被阻塞,直到事务A释放了锁。这样,事务A再次查询时,就不会看到那些原本不存在的“幻读”行.

参考


  • 《MySQL 技术内幕 InnoDB 存储引擎第 2 版》
  • Innodb 中的事务隔离级别和锁的关系
  • MySQL 事务与 MVCC 如何实现的隔离级别
  • InnoDB 事务分析-MVCC
  • InnoDB存储引擎对MVCC的实现 | JavaGuide

 


http://www.kler.cn/news/330194.html

相关文章:

  • (28)oracle数据迁移(容器)-部署包资源
  • Linux驱动开发(速记版)--设备树
  • 滚雪球学Oracle[2.3讲]:Oracle Listener配置与管理
  • 路由交换实验指南
  • MongoDB的安装与增删改查基本操作
  • Mars的xlog与Logan
  • JMeter中线程组、HTTP请求的常见参数解释
  • 欧科云链OKLink相约TOKEN2049:更全面、多元与安全
  • scrapy爬虫基础
  • n!尾随零的数量
  • PHP反射机制
  • 消息队列10:为RabbitMq添加连接池
  • Linux 下 select 详解
  • 微服务实战——平台属性
  • Visual studio2019+PCL1.11.1+win10
  • 畅阅读小程序|畅阅读系统|基于java的畅阅读系统小程序设计与实现(源码+数据库+文档)
  • 媲美GPT-4o mini的小模型,Meta Llama 3.2模型全面解读!
  • linux桌面软件(wps)内嵌到其他窗口
  • idea环境下vue2升级vue3
  • PHP魔幻(术)方法