超详细解读:数据库MVCC机制

之前文章:Mysql锁_exclusivelock for update写锁-CSDN博客   中有提到通过MVCC来实现快照读,从而解决幻读问题,这里详细介绍下MVCC。

一、前言

表1:实例表t
idk
11
22
表2:事务A、B、C的执行流程
事务A事务B事务C
start transaction with consistent snaption
start transaction with consistent snaption
update t set k=k+1 where id =1

update t set k=k+1 where id =1;

select k from t where id =1;

select k from t where id =1;

commit;

commit

先看上面执行流程,先思考下事务A和B两次查询结果都是什么。

注:

1、begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作InnoDB表的语句(第一个快照读语句),事务才真正启动。这里使用start transaction with consistent snaption命令立即开始事务。

2、事务C没有使用命令开启事务,因为update语句本身就是一个事务,执行完毕后执行commit

二、MVCC 的核心思想

        MVCC 的核心是通过 数据多版本 和 一致性视图(Consistent Read View) 来实现高并发下的读写隔离。其核心思想是:

  1. 每个数据行有多个版本,每次更新生成新版本,旧版本通过 undo log 保留。

  2. 事务根据可见性规则判断应读取哪个版本,而非直接读取最新数据。

    1. 事务ID与行版本

    • 事务ID(Transaction ID):每个事务启动时,InnoDB会为其分配一个全局唯一且递增的ID(trx_id)。

    • 行数据的版本:每次事务修改数据时,会生成一个新的数据版本,并将事务ID记录在该版本的 row trx_id 字段中。旧版本的数据通过 Undo Log 保存,形成版本链。

                                                            图1:行状态变更图

            上图就是一个记录被多个事务连续更新后的状态。图中虚线框里是同一行数据的4个版本,当前最新版本是V4,k的值是22,它是被transaction id 为25的事务更新的,因此它的row trx_id也是25。U1,U2,U3则是undo log的记录的日志。

    2. 一致性读视图(Consistent Read View)

           事务启动时(RR 级别)或语句执行时(RC 级别),InnoDB 会生成一个 一致性视图,用于判断数据版本的可见性。

    Read View 的四大核心属性

    1. trx_ids(活跃事务 ID 集合)

    • 含义:生成 Read View 时,当前系统中所有未提交的活跃事务 ID 的集合。
    • 作用:用于判断数据版本的事务是否在 Read View 生成时处于活跃状态。若在集合中,则该版本对当前事务不可见(除了自身事务,自身事务对于表的修改对于自己当然是可见的)。

    2. up_limit_id(最小活跃事务 ID)

    • 含义trx_ids集合中的最小事务 ID。
    • 作用:若数据版本的事务 ID < low_trx_id → 该版本在 Read View 生成前已提交,可见。

    3. low_limit_id(最大事务 ID 上限)

    • 含义:生成 Read View 时,系统中尚未分配的下一个事务 ID(并非实际存在的事务 ID)。
    • 作用:若数据版本的事务 ID ≥ up_trx_id → 该版本在 Read View 生成后才被创建,不可见。

    4. creator_trx_id(当前事务 ID)

    • 含义:生成该 Read View 的当前事务 ID。
    • 作用:若数据版本的事务 ID == creator_trx_id → 当前事务自己修改的数据,可见。

    数据可见性判断规则

    当事务读取一行数据时,需根据以下条件判断版本是否可见:

    1. 版本事务 ID < up_limit_id → 可见(已提交且早于 Read View 生成)。
    2. 版本事务 ID >= low_limit_id → 不可见(生成时间晚于 Read View)。
    3. 版本事务 ID 在 [up_limit_id, low_limit_id) 区间内
      • 若在 trx_ids中 → 不可见(活跃未提交)。
      • 若不在 trx_ids中 → 可见(已提交且在 Read View 生成后提交)。
    4. 版本事务 ID == creator_trx_id → 可见(当前事务自己修改的)。

    注意:一旦一个Read View被创建,这三个参数将不再发生变化,其中low_limit_id 和 up_limit_id分别是 trx_Ids数组的上下界(注意:从单词上来区分的话很容易弄反)。

    三、MVCC 如何实现隔离级别

    1. 可重复读(RR)

    • 视图创建时机:事务启动时创建一致性视图,后续所有读操作基于此视图。

    • 效果:事务内看到的数据始终一致,不受其他事务提交的影响。

    示例分析:

    如上面表格2中:

    假设事务开始前,当前活跃事务id=99,则事务A、B、C的事务id依次是100、101、102,事务开始前id=1 k=1的这一行数据的row trx_ids是90。(版本V1)

    那么,我们看下在 RR 隔离级别下执行过程:

    视图建立时,事务A的trx_ids=[99,100],同样事务B的trx_ids=[99,100,101]、事务C的trx_ids=[99,100,101,102]。先执行事务C的update,当前版本从V1(k=1)变成V2(k=2),V1则变为历史版本,执行事务B的update,历史版本从V2(k=2)变成V3(k=3),V2变成历史版本。

    这里为啥执行事务B,从k=2变成3,不是事务隔离吗?这个我们在后面解释。

    事务B执行查询操作时:

    trx_ids: [99,100,101]

    up_limit_id: [99]

    low_limit_id: [102]

    creator_trx_id=101,先查看V3版本,事务id=101在trx_ids中,但是等于我们当前事务,所以可见,所以最后结果k=3 

    事务A执行查询操作时:

    trx_ids: [99,100]

    up_limit_id: [99]

    low_limit_id: [101]

    creator_trx_id=100,先查看V3版本,事务id=101 >= low_limit_id 不可见,然后查找V2版本,事务id=102 >= low_limit_id,不可见,在查找V1版本,事务id=90,不在trx_ids中,可见。所有我们通过undo log,从V3 -> V2 -> V1,我们获取数据,最后结果k=1

    这样执行下来,虽然期间这一行数据被修改过,但是事务A不论在什么时候查询,看到这行数据的结果都是一致的,所以我们称之为一致性读

    2. 读提交(RC)

    • 视图创建时机:每条语句执行前重新生成一致性视图。

    • 效果:每次查询能看到已提交的最新数据。

    示例分析

    表2中的“start transaction with consistent snapshot; ”的意思是从这个语句开始,创建一个持续整个事务的一致性快照。所以,在读提交隔离级别下,这个用法就没意义了,等效于普通的start transaction。

    在 RC 隔离级别下:

    事务B的查询结果和RR一致。

    • 事务 C 已提交(ID=102),不在活跃事务数组中。

    • 事务 B 未提交(ID=101),仍在活跃事务数组中。

    • 事务 A 执行查询时:

      • trx_ids:[101](仅事务 B 未提交)。

      • up_limit_id:101(活跃事务最小 ID)。

      • low_limit_id:103(当前最大事务 ID=102,+1 后为 103)。

      数据可见性判断

      • 若数据的最新版本由事务 B(ID=101)更新:

        • row trx_id=101 在活跃数组中,不可见

        • 继续查找历史版本,找到事务 C(ID=102)提交的版本:

          • row trx_id=102 < 高水位(103),且不在活跃数组中,可见

      • 因此,事务 A 的第一次查询会读到事务 C 提交后的数据,即k=2。

    如果事务B提交后,事务A再执行一次查询呢?

     事务 B 提交后

    • 事务 B 提交后,ID=101 不再属于活跃事务。

    • 事务 A 执行第二次查询:

      • 活跃事务数组[](无未提交事务)。

      • 低水位:无(数组为空)。

      • 高水位:103(最大事务 ID 仍为 102,+1 后为 103)。

      数据可见性判断

      • 数据的最新版本由事务 B(ID=101)提交:

        • row trx_id=101 < 高水位(103),且不在活跃数组中,可见

      • 因此,事务 A 的第二次查询会读到事务 B 提交后的数据。

    四、当前读与一致性读

    MVCC 的读操作分为两种模式:

    1. 一致性读(Consistent Read):基于视图读取历史版本,用于普通 SELECT

    2. 当前读(Current Read):读取最新数据并加锁,用于更新操作(如 UPDATESELECT ... FOR UPDATE)。

    为什么更新需要当前读?

    假设事务 B 要更新数据:

    • 若使用一致性读,可能基于旧版本数据计算新值,导致其他事务的更新丢失。

    • 因此,更新操作必须读取最新版本(当前读),并对记录加锁,确保数据一致性。

    所以这里可以解释,为什么事务B执行update操作k是从2变成3,读到了事务C提交的数据,因为在更新的时候,当前读拿到的数据是(k=2),更新后生成了新版本的数据(k=3),这个新版本的row trx_id是101。所以,在执行事务B查询语句的时候,一看自己的版本号是101,最新数据的版本号也是101,是自己的更新,可以直接使用,所以查询得到的k的值是3。

    五、案例分析

    除了文章开始案例,我们这里再列举几个案例分析下。

    案例1

    事务A事务B事务C
    start transaction with consistent snaption
    start transaction with consistent snaption

    start transaction with consistent snaption;

    update t set k=k+1 where id =1;

    update t set k=k+1 where id =1;

    select k from t where id =1;

    select k from t where id =1;

    commit;

    commit
    commit

    我们看上面实例,跟前面分析相比,事务C执行update操作后并没有立即提交,那么如何执行呢。

    这里我们需要介绍二阶段协议:

     在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。

    执行流程:

    1. 事务C启动

      • 更新 id=1 的行,将 k 从 1 改为 2,但未提交(持有行锁)。

      • 此时数据版本链为:V1(row trx_id=90, k=1) → V2(row trx_id=102, k=2)(未提交)。

    2. 事务B启动

      • 尝试执行 UPDATE t SET k=k+1 WHERE id=1,需要获取行锁。

      • 因事务C未提交,事务B被阻塞,进入锁等待状态

    3. 事务A启动(RR隔离级别):

      • 执行 SELECT k FROM t WHERE id=1

      • 根据一致性视图规则,事务A的视图数组包含启动时活跃事务(如事务C的ID=102)。

      • 数据版本链中,V2 的 row trx_id=102 在活跃事务数组中,不可见;最终读取 V1k=1)。

    4. 事务C提交

      • 提交后释放行锁,数据版本 V2 的 row trx_id=102 变为已提交。

      • 事务B获得锁,执行当前读,读取最新版本 V2k=2),更新为 k=3,生成新版本 V3(row trx_id=101)

    5. 事务A再次查询

      • 仍基于启动时的视图,不可见事务B和C'的提交,结果仍为 k=1

    6. 事务B提交

      • 提交后数据版本 V3 的 row trx_id=101 变为已提交。

      • 新事务查询会看到 k=3

    注意:上面没有死锁风险,因为只有事务C和事务B在竞争同一行的锁,且是单向等待(事务B等待事务C释放锁),无循环依赖,因此不会死锁。

    案例2

    事务A事务B事务C
    start transaction with consistent snaption
    start transaction with consistent snaption

    start transaction with consistent snaption;

    update t set k=k+1 where id =1;

    update t set k=k+1 where id =2;

    select k from t where id =1;

    commit;

    update t set k=k+1 where id =2;

    update t set k=k+1 where id =1;
    commitcommit

    如果出现上面场景呢?

    执行流程

    1. 事务C:更新行id=1 → 持有行1的锁。

    2. 事务B:更新行id=2 → 持有行2的锁。

    3. 事务C:尝试更新行2 → 等待事务B释放行2的锁。

    4. 事务B:尝试更新行1 → 等待事务C释放行1的锁。

    此时,事务B和事务C互相等待对方释放资源,形成循环依赖,触发死锁。

    死锁相关分析可以参考:Mysql死锁_mysql 死锁的条件-CSDN博客

    案例3

    事务A事务B
    begin
    select k from t
    update t set k=k+1

    update t set k=0 where id = k;

    select k from t;

    上面运行结果:数据库会拒绝事务A的修改(如报错或阻塞),“数据无法修改”。

    六、MVCC 的优缺点

    优点

    • 高并发:读写不互相阻塞,读操作无需加锁。

    • 避免脏读和不可重复读:通过版本链和可见性规则实现隔离。

    缺点

    • 存储开销:需保留多个数据版本和 undo log。

    • 长事务问题:长事务可能导致大量历史版本无法清理,占用存储空间。


    六、实际应用建议

    1. 避免长事务:监控 information_schema.innodb_trx,及时终止长时间未提交的事务。

    2. 优先使用 RC 隔离级别:若业务允许,RC 比 RR 更节省资源。

    3. 更新前显式加锁:如需确保数据一致性,使用 SELECT ... FOR UPDATE 明确加锁。

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/77218.shtml

    如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

    相关文章

    【SpringCloud】从入门到精通【上】

    今天主播我把黑马新版微服务课程MQ高级之前的内容都看完了&#xff0c;虽然在看视频的时候也记了笔记&#xff0c;但是看完之后还是忘得差不多了&#xff0c;所以打算写一篇博客再温习一下内容。 课程坐标:黑马程序员SpringCloud微服务开发与实战 微服务 认识单体架构 单体架…

    力扣hot100_回溯(2)_python版本

    一、39. 组合总和&#xff08;中等&#xff09; 代码&#xff1a; class Solution:def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:ans []path []def dfs(i: int, left: int) -> None:if left 0:# 找到一个合法组合ans.append(pa…

    AI平台如何实现推理?数算岛是一个开源的AI平台(主要用于管理和调度分布式AI训练和推理任务。)

    数算岛是一个开源的AI平台&#xff0c;主要用于管理和调度分布式AI训练和推理任务。它基于Kubernetes构建&#xff0c;支持多种深度学习框架&#xff08;如TensorFlow、PyTorch等&#xff09;。以下是数算岛实现模型推理的核心原理、架构及具体实现步骤&#xff1a; 一、数算岛…

    cesium项目之cesiumlab地形数据加载

    之前的文章我们有提到&#xff0c;使用cesiumlab加载地形出现了一些错误&#xff0c;没有解决&#xff0c;今天作者终于找到了解决方法&#xff0c;下面描述一下具体步骤&#xff0c;首先在地理数据云下载dem数据&#xff0c;在cesiumlab中使用地形切片&#xff0c;得到terrain…

    [Vue]App.vue讲解

    页面中可以看见的内容不再在index.html中进行编辑&#xff0c;而是在App.vue中进行编辑。 组件化开发 在传统的html开发中&#xff0c;一个页面的资源往往都写在同一个html文件中。这种模式在开发小规模、样式简单的项目时会相当便捷&#xff0c;但当项目规模越来越大&#xf…

    sql-labs靶场 less-1

    文章目录 sqli-labs靶场less 1 联合注入 sqli-labs靶场 每道题都从以下模板讲解&#xff0c;并且每个步骤都有图片&#xff0c;清晰明了&#xff0c;便于复盘。 sql注入的基本步骤 注入点注入类型 字符型&#xff1a;判断闭合方式 &#xff08;‘、"、’、“”&#xf…

    蓝桥杯-小明的彩灯(差分)

    问题描述&#xff1a; 差分数组 1. 什么是差分数组&#xff1f; 差分数组 c 是原数组 a 的“差值表示”&#xff0c;其定义如下&#xff1a; c[0] a[0]c[i] a[i] - a[i-1] &#xff08;i ≥ 1&#xff09; 差分数组记录了相邻元素的差值。例如&#xff0c;原数组 a [1, …

    精品可编辑PPT | 基于湖仓一体构建数据中台架构大数据湖数据仓库一体化中台解决方案

    本文介绍了基于湖仓一体构建数据中台架构的技术创新与实践。它详细阐述了数据湖、数据仓库和数据中台的概念&#xff0c;分析了三者的区别与协作关系&#xff0c;指出数据湖可存储大规模结构化和非结构化数据&#xff0c;数据仓库用于高效存储和快速查询以支持决策&#xff0c;…

    最近api.themoviedb.org无法连接的问题解决

    修改NAS的host需要用到SSH终端连接工具&#xff0c;比如常见的Putty&#xff0c;XShell&#xff0c;或者FinalShell等都可以&#xff0c;我个人还是习惯Putty。 1.输入命令“ sudo -i ”回车&#xff0c;提示输入密码&#xff0c;密码就是我们NAS的登录密码&#xff0c;输入的…

    0.机器学习基础

    0.人工智能概述&#xff1a; &#xff08;1&#xff09;必备三要素&#xff1a; 数据算法计算力 CPU、GPU、TPUGPU和CPU对比&#xff1a; GPU主要适合计算密集型任务&#xff1b;CPU主要适合I/O密集型任务&#xff1b; 【笔试问题】什么类型程序适合在GPU上运行&#xff1…

    多类型医疗自助终端智能化升级路径(代码版.下)

    医疗人机交互层技术实施方案 一、多模态交互体系 1. 医疗语音识别引擎 # 基于Wav2Vec2的医疗ASR系统 from transformers import Wav2Vec2Processor, Wav2Vec2ForCTC import torchaudioclass MedicalASR:def __init__(self):self.processor = Wav2Vec2Processor.from_pretrai…

    前端基础:React项目打包部署服务器教程

    问题背景 我做了一个React框架的前端的Node项目&#xff0c;是一个单页面应用。 页面路由用的是&#xff0c;然后使用了React.lazy在路由层级对每一个不同页面进行了懒加载&#xff0c;只有打开那个页面才会加载对应资源。 然后现在我用了Webpack5对项目进行了打包&#xff…

    【深度学习:理论篇】--Pytorch基础入门

    目录 1.Pytorch--安装 2.Pytorch--张量 3.Pytorch--定义 4.Pytorch--运算 4.1.Tensor数据类型 4.2.Tensor创建 4.3.Tensor运算 4.4.Tensor--Numpy转换 4.5.Tensor--CUDA&#xff08;GPU&#xff09; 5.Pytorch--自动微分 &#xff08;autograd&#xff09; 5.1.back…

    使用 Spring Boot 快速构建企业微信 JS-SDK 权限签名后端服务

    使用 Spring Boot 快速构建企业微信 JS-SDK 权限签名后端服务 本篇文章将介绍如何使用 Spring Boot 快速构建一个用于支持企业微信 JS-SDK 权限校验的后端接口&#xff0c;并提供一个简单的 HTML 页面进行功能测试。适用于需要在企业微信网页端使用扫一扫、定位、录音等接口的…

    工程师 - FTDI SPI converter

    中国网站&#xff1a;FTDIChip- 首页 UMFT4222EV-D UMFT4222EV-D - FTDI 可以下载Datasheet。 UMFT4222EVUSB2.0 to QuadSPI/I2C Bridge Development Module Future Technology Devices International Ltd. The UMFT4222EV is a development module which uses FTDI’s FT4222H…

    rcore day6

    批处理系统 (Batch System) 出现于计算资源匮乏的年代&#xff0c;其核心思想是&#xff1a; 将多个程序打包到一起输入计算机&#xff1b;当一个程序运行结束后&#xff0c;计算机会 自动 执行下一个程序 应用程序难免会出错&#xff0c;如果一个程序的错误导致整个操作系统都…

    Linux系统学习Day2——在Linux系统中开发OpenCV

    一、OpenCV简介 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的跨平台计算机视觉和机器学习库&#xff0c;广泛应用于图像处理、视频分析、物体检测等领域。它提供了丰富的算法和高效的工具集&#xff0c;支持C、Python等多种语言&#xff0c…

    SAP Overview

    SAP—企业运营的数字化引擎 在数字化转型的浪潮中&#xff0c;SAP以其全面的企业应用软件套件&#xff0c;为全球企业提供了强大的运营支持。SAP的模块化解决方案覆盖了企业运作的每一个关键环节&#xff0c;从销售到仓库管理&#xff0c;每个模块都是针对特定业务需求精心设计…

    Kafka 中的幂等机制

    Kafka 中的 幂等性&#xff08;Idempotence&#xff09; 是生产者端的重要机制&#xff0c;旨在确保即使在网络抖动、重试、Broker 重启等情况下&#xff0c;同一条消息不会被重复写入到 Topic 中。这是实现可靠消息传递、避免重复消费的关键手段之一。 ✅ 什么是幂等性&#…

    用c语言写一个linux进程之间通信(聊天)的简单程序

    使用talk 用户在同一台机器上talk指令格式如下&#xff1a; ​ talk 用户名ip地址 [用户终端号] 如果用户只登录了一个终端&#xff0c;那么可以不写用户终端号&#xff0c;如&#xff1a; talk userlocalhost可以使用who指令来查看当前有哪些用户登录&#xff0c;他的终端号…