聊多版本并发控制(MVCC)

本文涉及的产品
PolarClaw,2核4GB
RDS AI 助手,专业版
RDS DuckDB + QuickBI 企业套餐,8核32GB + QuickBI 专业版
简介: MVCC是数据库并发控制技术,用于减少读写冲突。它维护数据的多个版本,使事务能读旧数据而写新数据,无需锁定记录。当前读获取最新版本,加锁防止修改;快照读不加锁,根据读取时的读视图(readview)决定读哪个版本。InnoDB通过隐藏字段(DB_TRX_ID, DB_ROLL_PTR)和undo log存储版本,readview记录活跃事务ID。读已提交每次读取都创建新视图,可重复读则在整个事务中复用一个视图,确保一致性。MVCC通过undo log版本链和readview规则决定事务可见性,实现了非阻塞并发读。

多版本并发控制(MVCC)

MVCC一直是数据库部分的高频面试题,这篇文章来聊聊MVCC是什么,以及一些底层原理的实现。

当前读和快照读:

  • 当前读:读取的是事务最新的版本,读取的过程中其他并发事务不能进行修改,需要对读取的记录进行加锁。
  • 快照读:不加任何锁的select语句就是快照读。假如在可重复读隔离级别下,事务A进行select先后两次读取,而事务B在两次读取过程中修改了数据并提交了事务,那么事务A读到的就不是最新版本的数据。如果想读到最新版本的数据,需要在读取的过程中加锁,如select ... lock in share mode.
    • 读已提交隔离级别下:每次select都是一次快照读,因为每次读都可能读取到的是其他事务已经提交的增删改操作
    • 可重复读隔离级别下:开启事务后第一次select才是快照读,因为其后的select读取不到其他事务提交的增删改操作
    • 串行化隔离级别下:每次select都是一次当前读,因为每次读取都会加锁

MVCC的概念:

  • MVCC即多版本并发控制,维护一个数据的多个版本,使得不同事务之间的读写操作没有冲突。
  • 什么是版本:一行记录修改前和修改后就是两个不同的版本。

MVCC的实现原理:

  • MVCC依靠三样东西来实现:隐藏字段undo logreadview
  • 创建一个表的时候InnoDB会给表加上三个隐藏字段**DB_TRX_IDDB_ROLL_PTRDB_ROLL_ID**
    • DB_TRX_ID:最近一次修改这一行记录的事务的id。比如DB_TRX_ID = 4,表示最近一次修改这行 记录的事务是4。
    • DB_ROLL_PTR:回滚指针,指针指向这行记录的上一个版本,用于配合undo log回滚日志来找到这行记录的上一个版本(执行增删改之前的版本)
    • DB_ROLL_ID:隐藏主键。如果表没有指定主键的时候会自动生成。如果已经指定了主键,就不会生成。

image-20240330182341116.png

  • undo log:在insert的时候,undo log只在回滚的时候需要,在事务提交后可以立即删除,不需要记录上一个版本该行的数据(因为上一版本该行不存在;在updatedelete的时候,不仅回滚的时候需要,多版本并发控制的时候也需要用来记录上一数据版本,用于多版本并发控制,因此事务提交后也不会立即删除。
    • undo log版本链

image-20240330192626725.png

  • readview:每次事务执行快照读都有可能会生成readview。决定快照读读取的是undo log版本链中的哪一条历史记录。readview记录的是当前活跃中(未提交)的事务的id,包含以下四个字段:
    • m_ids:当前正在活跃中的事务的集合
    • min_trx_id:最小的活跃中的事务id
    • max_trx_id:不是当前正在活跃中的事务集合的最大id,而是预分配的事务id,即最大活跃中的事务id+1(因为事务id是自增的,因此加一)
    • creator_trx_id:readview创建者的事务id
    • 版本链数据的访问规则:trx_id:undo log记录的当前事务的id,即隐藏字段DB_TRX_ID,用这个id去匹配下面的规则,如果符合其中某一条件,说明当前版本是可以读取的;如果不符合条件,说明这个版本的数据不能读,就会顺着版本链去寻找更老的版本的数据
      • 如果trx_id = creator_trx_id:说明数据就是当前事务修改的,因此是可以读取到的
      • 如果trx_id < min_trx_id:说明当前事务不是活跃中的事务(已经提交),已提交的数据版本是可以读取到的
      • 如果trx_id > max_trx_id:说明当前事务是在readview生成后才开启的(?),因此不可以访问该版本的数据
      • 如果min_trx_id < trx_id <= max_trx_id且 trx_id不属于活跃中的事务的集合,说明该事务数据已经提交,已提交的数据版本是可以读取到的
      • readview的生成时机:不同隔离级别生成时机不同。如果是读已提交隔离级别,那么在事务中每次执行快照读都会生成readview;如果是可重复读隔离级别,那么只在事务第一次执行快照读的时候生成readview,后续会复用这个readview

MVCC演示:

  • 读已提交隔离级别:在这种隔离级别下,每一次快照读都会生成一个readview,在事务5中,第一次快照读会有三个活跃中的事务3、4、5;第二次快照读由于事务3已经提交,因此只有两个活跃中的事务4和5。
    • 第一次快照读,此时有三个活跃中的事务3、4、5;
      • 最近一次对id = 30这行记录进行修改的事务id是4,即trx_id = 4,均不符合版本链数据的访问规则,因此顺着版本链去寻找更老版本的数据,即trx_id = 3;
      • trx_id = 3,也均不符合版本链数据的访问规则,继续寻找trx_id = 2;
      • trx_id = 2时,符合第二条规则,trx_id(2) < min_trx_id(3),小于最小的正在活跃中的事务id,说明trx_id = 2这个事务已经提交了,结合读已提交隔离级别的宏观理解,并发事务是可以读取到其他事务已经提交的数据的,因此查询结果就是trx_id = 2对应的版本
    • 第二次快照读,此时有两个活跃中的事务4、5。
      • 最近一次对id = 30这行记录进行修改的事务id是4,即trx_id = 4,均不符合版本链数据的访问规则,因此顺着版本链去寻找更老版本的数据,即trx_id = 3;
      • trx_id = 3时,符合第二条规则,trx_id(3) < min_trx_id(4),小于最小的正在活跃中的事务id,说明trx_id = 3这个事务已经提交了,结合读已提交隔离级别的宏观理解,并发事务是可以读取到其他事务已经提交的数据的,因此查询结果就是trx_id = 3对应的版本

image-20240330205128148.png

可重复读隔离级别:在这种隔离级别下,只在事务第一次执行快照读的时候生成readview,后续再读取的时候会复用这个readview。以下过程和读已提交隔离级别的过程相同,唯一的区别是读已提交隔离级别两次执行快照读都会生成新的readview,因而两次读取的结果不同;而可重复读隔离级别两次执行快照读的readview都相同,因而读取结果相同。上述过程就是不可重复读的底层原理。

  • 第一次快照读,此时有三个活跃中的事务3、4、5;
    • 最近一次对id = 30这行记录进行修改的事务id是4,即trx_id = 4,均不符合版本链数据的访问规则,因此顺着版本链去寻找更老版本的数据,即trx_id = 3;
    • trx_id = 3,也均不符合版本链数据的访问规则,继续寻找trx_id = 2;
    • trx_id = 2时,符合第二条规则,trx_id(2) < min_trx_id(3),小于最小的正在活跃中的事务id,说明trx_id = 2这个事务已经提交了,结合读已提交隔离级别的宏观理解,并发事务是可以读取到其他事务已经提交的数据的,因此查询结果就是trx_id = 2对应的版本
  • 第二次快照读复用第一次采用的readview,因此读取结果相同。

image-20240330205350114.png

目录
相关文章
|
存储 关系型数据库 MySQL
MVCC多版本并发控制
MVCC多版本并发控制 1、MVCC MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
859 0
|
监控 算法
探秘Guava的RateLimiter:单机流量控制的黄金法宝
探秘Guava的RateLimiter:单机流量控制的黄金法宝
734 0
|
安全 Java
【深入理解同步器AQS】
【深入理解同步器AQS】
654 0
|
缓存 Java 数据库连接
深入浅出 MyBatis 的一级、二级缓存机制
深入浅出 MyBatis 的一级、二级缓存机制
1131 0
|
监控 Java Linux
JVM调优
JVM调优
806 0
|
1月前
|
存储 缓存 安全
synchronized 底层全解:从对象头、锁升级到内核实现,击穿并发编程的核心基石
本文深度剖析Java中synchronized的底层原理:从三种使用范式、字节码实现,到对象内存布局、Mark Word状态切换,详解锁升级(偏向→轻量→重量)全流程及JVM优化(锁消除/粗化),并结合JOL实战验证,兼顾理论深度与生产实用性。
350 2
|
SQL 关系型数据库 数据库
学习分布式事务Seata看这一篇就够了,建议收藏
学习分布式事务Seata看这一篇就够了,建议收藏
24194 2
|
22天前
|
SQL 存储 监控
避坑必看!MySQL 三大日志(redo/undo/binlog)底层原理全拆解,事务一致性再也不懵
MySQL事务ACID的基石是redo、undo、binlog三大日志:redo log保障持久性(宕机不丢数据),undo log保障原子性(支持回滚与MVCC),binlog保障可追溯与主从同步。三者协同工作,缺一不可。
318 3
|
7月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
2568 0
|
5月前
|
监控 Java 开发者
Spring Boot 核心原理解析与实践(含代码示例)
Spring Boot基于“约定优于配置”理念,通过自动配置、Starter依赖和内嵌服务器,简化Spring应用的搭建与开发。支持快速集成Web、数据访问、安全等模块,并提供Actuator监控、分布式事务等生产级特性,助力高效构建微服务系统。(238字)
1292 17