大厂最爱问的MVCC,到底是个啥?

news2024/9/22 17:35:18

引言

多版本并发控制(MVCC)是一种用于提高数据库并发性能的技术,尤其在处理高并发读写操作时极为有效。MVCC通过维护数据的多个版本来避免读写冲突,使得读操作无需阻塞写操作,写操作也不会影响读操作。下面,我们具体讲解MySQL中InnoDB存储引擎对MVCC的实现原理。

MySQL

MVCC 的基本概念

MVCC(Multi-Version Concurrency Control)可以看作是行级锁的一种改进,主要通过保存数据在不同时间点的多个版本来实现数据的并发访问。MVCC 常用于实现乐观锁定策略,通过版本号控制数据的一致性,避免了因锁导致的性能瓶颈。

不知道大家是否想过这样一个问题:在日常操作中,为什么读写操作可以互不阻塞?

在MVCC技术出现之前,为了确保在特定隔离级别下,多个事务之间不发生数据异常,需要通过加锁来控制并发。

例如,当事务A正在读取一行数据时,其他事务不能对这行数据进行修改,因为这可能导致事务A读取到不一致的数据。

MVCC的核心优势就在于解决了读写操作之间的阻塞问题,从而显著提高了事务的并发性。接下来,我们通过一个例子来逐步学习MySQL中MVCC的实现。

我们来看图1中的这个例子;

  1. 在Session1中对某条记录进行了修改但尚未提交时,此时在Session2中执行查询,返回的记录会是什么呢?
  2. 而当Session1提交后,session2再次查询时结果又会如何变化呢?

图1

要回答这个问题,关键在于事务的隔离级别,不同隔离级别下的行为如下:

  • READ-UNCOMMITTED 隔离级别:在session 1提交前后,session 2查询到的都是修改后的结果,即column= lisi
  • READ-COMMITTED 隔离级别:在session 1提交前,session 2查询到的仍是原始数据column= zhangsan

,但在提交后,查询结果变为column= lisi

  • REPEATABLE-READSERIALIZABLE 隔离级别:在session 1提交前后,session 2查询到的始终是修改前的结果column= zhangsan

以下是MySQL 5.7和MySQL 8.0中查询和修改数据库隔离级别语句的对比表格:

操作MySQL 5.7MySQL 8.0
查询当前会话隔离级别SELECT @@tx_isolation; 或 SHOW VARIABLES LIKE ‘transaction_isolation’;SELECT @@transaction_isolation; 或 SHOW VARIABLES LIKE ‘transaction_isolation’;
查询全局隔离级别SELECT @@global.tx_isolation; 或 SHOW GLOBAL VARIABLES LIKE ‘transaction_isolation’;SELECT @@global.transaction_isolation; 或 SHOW GLOBAL VARIABLES LIKE ‘transaction_isolation’;
修改当前会话隔离级别SET SESSION TRANSACTION ISOLATION LEVEL 隔离级别;SET SESSION TRANSACTION ISOLATION LEVEL 隔离级别;
修改全局隔离级别SET GLOBAL TRANSACTION ISOLATION LEVEL 隔离级别;SET GLOBAL TRANSACTION ISOLATION LEVEL 隔离级别;

其中,隔离级别可以是以下几种之一:

  • READ UNCOMMITTED
  • READ COMMITTED
  • REPEATABLE READ(MySQL默认)
  • SERIALIZABLE

在这里我们暂且不讨论数据库的ACID特性,而是关注一个问题:在数据被修改后,数据库是如何确保查询结果仍然保持为之前的值?

这正是通过数据库中的Undo日志和MVCC技术来实现的,如图1-1所示。

1-1

InnoDB 中的 MVCC 主要依赖于以下三个隐藏列,这些列在每行记录中维护,以实现多版本并发控制:

  1. DB_TRX_ID
    每条记录都包含一个 DB_TRX_ID,即事务ID,记录了最后一次对该行进行插入或更新的事务ID。当需要判断某个事务中某条记录是否可见时,InnoDB 会根据该列与当前事务的视图进行对比。
  2. DB_ROLL_PTR
    DB_ROLL_PTR 是一个回滚指针,指向该行记录的上一个版本。在数据更新时,InnoDB 会保留旧版本的记录,并通过这个指针将其链接起来,从而支持通过回滚找到之前的数据版本。这是实现 MVCC 中多版本存储的关键。
  3. DB_ROW_ID
    DB_ROW_ID 是一个自增的行ID,在没有显式主键的情况下,用来唯一标识每一行数据。尽管 DB_ROW_ID 在 MVCC 机制中并不直接控制并发,但它对行记录的管理和定位起着重要作用,尤其是在没有定义主键时。

这三个隐藏列共同支撑了 InnoDB 的 MVCC 实现,确保在高并发场景下,读写操作能够并行进行而不互相阻塞。

当插入一条数据时, 在记录上对应的回滚段指针为NULL, 如图1-2所示

1-2

在更新记录时,原始记录会被保存到 Undo 表空间中,查询时未提交的修改数据可以通过读取 Undo 表空间中的旧版本来实现。多个数据版本通过链表结构链接,形成一个版本链。MySQL 通过记录中的回滚指针(DB_ROLL_PTR)和事务ID(DB_TRX_ID)来判断数据版本的可见性。具体判断流程如下:

判断流程

在每个事务开始时,系统会将当前所有活跃事务的信息拷贝到一个列表中,称为Read View。读取记录时,会根据记录的 TRX_ID 与 Read View 中的最大 TRX_ID 和最小 TRX_ID 进行比较,判断该记录是否对当前事务可见。

  1. 如果记录的 TRX_ID 小于 Read View 中的最小 TRX_ID**********:**
    说明该记录在 Read View 中的所有事务开始之前就已提交,因而对当前事务可见,可以直接返回数据。
  2. 如果记录的 TRX_ID 大于 Read View 中的最大 TRX_ID**********:**
    说明该记录在事务启动后被修改。此时需要根据回滚指针找到前一个版本的记录,并将其 TRX_ID 赋值给当前行,再重新进行判断。
  3. 如果记录的 TRX_ID 位于 Read View 的范围内:

需要进一步判断:

  • 如果 TRX_ID 在 Read View 中存在:
    • 这意味着该记录在事务启动时是活跃的,未提交。此时根据回滚指针找到前一个版本的记录,取出其 TRX_ID 进行下一轮判断。
  • 如果 TRX_ID 不在 Read View 中:
    • 说明该记录在事务启动时已提交,因此对当前事务可见,可以直接返回数据。

什么是 Read View

Read View 是 MySQL InnoDB 引擎用于实现 多版本并发控制 (MVCC) 的关键机制。它在事务执行快照读时生成,确保事务能够看到一致的、稳定的数据视图。Read View 记录了事务生成快照时的系统状态,尤其是所有活跃事务的 ID 列表。通过 Read View,数据库可以确保事务只看到在其生成视图时已经提交的数据,而不受之后其他事务的影响。

当前读和快照读

当前读 (Current Read)

  • 当前读需要获取记录的最新版本,并且在读操作期间可能会加锁,以防止其他事务同时修改这些记录。
  • 场景:SELECT ... LOCK IN SHARE MODE, SELECT ... FOR UPDATE, UPDATE, INSERT, DELETE 等。
  • 这些操作确保事务读取到的数据是最新的,并且避免其他事务在操作期间对数据进行并发修改。

快照读 (Snapshot Read)

  • 快照读通常指的是不加锁的 SELECT 操作,它通过 Read View 实现,确保事务读取到的总是快照生成时的数据版本,而不是之后的修改。
  • 场景:SELECT 操作,在隔离级别为读已提交 (Read Committed) 或可重复读 (Repeatable Read) 时尤其常见。
  • 在隔离级别为串行化 (Serializable) 时,快照读会退化为当前读,因为此时所有操作都必须确保数据的完全一致性,避免幻读现象。

Read View 的四个字段

Read View 的四个字段决定了事务在执行快照读时哪些数据对其可见,哪些不可见。

trx_ids:

  • trx_ids 是生成 Read View 时所有 活跃事务(即未提交事务)的 ID 列表。
  • 这些事务的更改对当前事务不可见。换句话说,Read View 生成后,这些事务对数据库的修改数据将不被该事务看到。

low_limit_id:

  • low_limit_id 是下一个将分配的事务 ID。
  • 由于事务 ID 是递增的,因此 low_limit_id 标志着比该 ID 更新的事务(即后续事务)的数据对当前 Read View 不可见。

up_limit_id:

  • up_limit_id 是生成 Read View 时已经提交的最小事务 ID。
  • 这个 ID 之前提交的所有事务产生的数据变更对当前事务可见,这也是判断数据版本可见性的边界之一。

creator_trx_id:

  • creator_trx_id 是创建 Read View 的事务 ID。
  • 这个字段用于在当前事务内判断自己提交的数据版本的可见性。例如,如果在事务中执行了多次快照读,creator_trx_id 帮助确定哪些数据版本是当前事务自己生成的,并对其可见。

Read View 通过以上四个字段来管理事务快照的可见性,保证了事务在快照读时的一致性。在 MySQL 的 MVCC 实现中,这一机制非常重要,因为它确保了在高并发环境下事务隔离的实现,防止脏读、不可重复读和幻读问题的发生。

源码解读

在 MySQL 8.0 的源码中,Read View 的核心代码主要涉及 InnoDB 存储引擎的事务管理部分。它们定义了 Read View 的生成、字段初始化和可见性判断等功能。

read_view_open_now 函数

read_view_open_now 函数是用来创建一个新的 Read View。它会记录当前活跃事务,并初始化 Read View 的各个字段。

read_view_t* read_view_open_now(trx_t* trx) {
    read_view_t* view;

    view = static_cast<read_view_t*>(ut_malloc_nokey(sizeof(*view)));
    ut_a(view != NULL);

    /* 设置创建者事务 ID */
    view->creator_trx_id = trx->id;

    /* 获取当前活跃事务列表并赋值给 view */
    view->m_trx_ids = trx_sys_get_active_trx_ids();

    /* 活跃事务的数量 */
    view->trx_list_len = trx_sys->rw_trx_list_len;

    /* 设置低水位 */
    view->low_limit_no = trx_sys->rw_trx_list->start->id;

    /* 设置高水位 */
    view->up_limit_no = trx_sys->rw_trx_list->end->id;

    return(view);
}

trx_sys_get_active_trx_ids 函数

trx_sys_get_active_trx_ids 函数用于获取当前系统中所有活跃事务的 ID。这个函数返回一个包含所有未提交事务 ID 的列表,这些事务的变更对当前 Read View 不可见。

trx_id_t* trx_sys_get_active_trx_ids() {
    trx_id_t* trx_ids;

    /* 分配用于存储事务 ID 的内存空间 */
    trx_ids = static_cast<trx_id_t*>(ut_malloc_nokey(trx_sys->rw_trx_list_len * sizeof(trx_id_t)));

    /* 遍历当前活跃事务列表,存储每个事务的 ID */
    rw_trx_list_lock();

    rw_trx_t* rw_trx = trx_sys->rw_trx_list->start;

    for (size_t i = 0; i < trx_sys->rw_trx_list_len; i++, rw_trx = rw_trx->next) {
        trx_ids[i] = rw_trx->id;
    }

    rw_trx_list_unlock();

    return trx_ids;
}

read_view_sees_trx_id 函数

read_view_sees_trx_id 函数用于判断某个事务 ID 的变更是否对当前 Read View 可见。它依据 Read View 的四个字段来决定可见性。

bool read_view_sees_trx_id(read_view_t* view, trx_id_t trx_id) {
    /* 如果事务 ID 小于 up_limit_id,说明该事务在 Read View 生成之前已经提交,因此可见 */
    if (trx_id < view->up_limit_no) {
        return true;
    }

    /* 如果事务 ID 大于或等于 low_limit_id,说明该事务在 Read View 生成之后启动,因此不可见 */
    if (trx_id >= view->low_limit_no) {
        return false;
    }

    /* 如果事务 ID 在 active_trx_ids 列表中,说明它是活跃事务之一,因此不可见 */
    for (size_t i = 0; i < view->trx_list_len; i++) {
        if (view->m_trx_ids[i] == trx_id) {
            return false;
        }
    }

    /* 如果上述条件都不满足,则该事务可见 */
    return true;
}

read_view_close 函数

read_view_close 函数用于关闭并销毁一个 Read View,释放内存。

void read_view_close(read_view_t* view) {
    ut_free(view->m_trx_ids);
    ut_free(view);
}

代码逻辑总结

创建 Read View (read_view_open_now):

  • 为事务生成一个新的 Read View,并初始化其所有字段,包括活跃事务列表、上下限 ID 等。

获取活跃事务 ID (trx_sys_get_active_trx_ids):

  • 遍历系统中所有未提交的事务,获取它们的 ID,并将这些 ID 存储在 Read View 中。

判断事务可见性 (read_view_sees_trx_id):

  • 判断某个事务 ID 是否对当前 Read View 可见。依据生成 Read View 时的系统状态(如事务 ID 上下限、活跃事务列表等)进行判断。

关闭 Read View (read_view_close):

  • 释放 Read View 相关的内存,结束 Read View 的生命周期。

这些核心代码段展示了 InnoDB 是如何通过 Read View 实现快照读,确保事务隔离和一致性。

MVCC相关实现

准备测试数据

在 Navicat 中执行以下 SQL,准备一张简单的测试表:

CREATE TABLE test_mvcc (
    id INT PRIMARY KEY,
    value VARCHAR(50)
) ENGINE=InnoDB;

INSERT INTO test_mvcc (id, value) VALUES (1, 'Initial Value');

在两个会话中进行操作

在 Navicat 中打开两个查询窗口,模拟两个事务会话。

会话 1(启动事务并更新记录):

START TRANSACTION;
UPDATE test_mvcc SET value = 'Updated Value by Session 1' WHERE id = 1;
-- 此时事务未提交,数据仅对会话 1 可见

会话 2(启动事务并查询记录):

START TRANSACTION;
SELECT * FROM test_mvcc WHERE id = 1;
-- 此时会话 2 应该仍然看到 'Initial Value',因为会话 1 的事务未提交

在会话 1 提交事务之前,数据对其他事务不可见。

提交事务并观察变化

会话 1 提交事务:

COMMIT;

会话 2 再次查询:

SELECT * FROM test_mvcc WHERE id = 1;
-- 现在会话 2 可以看到更新后的值 'Updated Value by Session 1'

分析可见性

可以通过这个实验观察事务之间的隔离性和记录的可见性,结合以下 MySQL 内置命令来查看更多细节:

-- 查看 InnoDB 的事务状态
SHOW ENGINE INNODB STATUS;

-- 查看当前事务的 ID 和隔离级别
SELECT @@TRANSACTION_ISOLATION, @@tx_isolation;

Navicat 可以帮助在图形界面中直观地管理和操作多个会话,但代码级别的调试,如跟踪 MySQL 源代码中具体的可见性判断过程,需要使用 GDB 等工具。

这里需要说明一点, 对于不同的事务隔离级别, 可见性的实现也不一样。

  • 对于READ-COMMITTED隔离级别, 事务内的每一条查询语句都会重新创建ReadView, 这样就会产生不可重复读现象。
  • 对于REPEATABLE-READ隔离级别, 事务内第一条语句执行时会创建ReadView, 在事务结束这段时间内每一次查询都不会重新创建ReadView, 从而实现了可重复读。

参考资料: https://dev.mysql.com/blog-archive/mysql-8-0-mvcc-of-large-objects-in-innodb/

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

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

相关文章

内网环境使用Docker部署Qwen2模型-vLLM篇

在此之前&#xff0c;我们已成功利用Docker与Ollama框架&#xff0c;在内网环境中部署了Qwen2模型。下面我们再来看一下使用Docker与vLLM框架部署Qwen2模型。 准备vLLM镜像 在一台具备网络环境的机器上执行以下命令&#xff0c;拉取vLLM的镜像&#xff1a; # 官方镜像 docke…

探索MySQL数据查询语言的无限魅力:精准检索,驾驭数据海洋的钥匙

作者简介&#xff1a;我是团团儿&#xff0c;是一名专注于云计算领域的专业创作者&#xff0c;感谢大家的关注 座右铭&#xff1a; 云端筑梦&#xff0c;数据为翼&#xff0c;探索无限可能&#xff0c;引领云计算新纪元 个人主页&#xff1a;团儿.-CSDN博客 目录 前言&#…

可视掏耳勺真的有作用吗?测评热门可视掏耳勺

随着现代人对个护健康的日益关注&#xff0c;可视掏耳勺这掏耳神器逐渐风靡市场&#xff0c;但同时也伴随着部分质疑的声音&#xff0c;甚至被贴上“智商税”的标签。因为有不少消费者使用时出现画质低清、材质不舒服等现象&#xff0c;那么&#xff0c;可视掏耳勺真的好用吗&a…

上海亚商投顾:沪指再创阶段新低 全市场下跌个股超4400只

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 沪指9月6日冲高回落&#xff0c;午后续创调整新低&#xff0c;创业板指跌1.7%。券商、保险等金融股逆势走强&a…

快速入门游戏领域,开发游戏需要哪些技术?

在这个充满创意和技术的时代&#xff0c;游戏行业成为众多创新人才追求梦想的热土。对于准备踏入这个充满挑战与机遇的领域的新人来说&#xff0c;了解游戏开发流程是至关重要的。 游戏市场蓬勃发展&#xff0c;游戏行业未来行情可观&#xff0c;在这个充满创意和技术的时代&a…

Swiper轮播图框架【前端 24】

Swiper轮播图框架 在如今的网页设计中&#xff0c;轮播图已成为一种不可或缺的元素&#xff0c;它能够以动态的方式展示图片、视频或文本信息&#xff0c;有效吸引用户的注意力并提升页面的互动性。在众多轮播图实现框架中&#xff0c;Swiper以其高度的可定制性、流畅的滑动效果…

浏览器百科:网页存储篇-如何在Chrome中打开IndexedDB窗格(十一)

1.引言 在现代Web开发中&#xff0c;网页存储技术扮演着至关重要的角色。IndexedDB作为一种低级API&#xff0c;允许客户端存储大量结构化数据&#xff0c;并提供高性能的搜索能力。在上一篇文章中&#xff0c;我们深入探讨了IndexedDB的基础知识及其应用场景。为了更有效地调…

回收玻璃减薄中的氢氟酸

回收玻璃减薄中的氢氟酸是一个重要的环保和资源再利用环节。在玻璃减薄过程中&#xff0c;氢氟酸作为主要的化学蚀刻剂&#xff0c;与玻璃基板表面的二氧化硅等成分发生反应&#xff0c;实现玻璃的减薄。然而&#xff0c;随着反应的进行&#xff0c;氢氟酸的浓度会逐渐降低&…

爆改YOLOv8|利用BiFPN双向特征金字塔改进yolov8

1&#xff0c;本文介绍 BiFPN&#xff08;Bidirectional Feature Pyramid Network&#xff09;是一种增强特征金字塔网络&#xff08;FPN&#xff09;的方法&#xff0c;旨在改善多尺度特征融合。BiFPN的主要创新点包括&#xff1a; 双向特征融合&#xff1a;与传统FPN仅在自下…

AI智能工牌:告别手动录入,1小时上门服务报告,3分钟生成

在当今快速发展的商业环境中&#xff0c;提高工作效率和客户满意度成为了企业追求的核心目标之一。传统的手动录入方式不仅耗时耗力&#xff0c;而且容易出错&#xff0c;特别是在上门服务行业&#xff0c;如何快速准确地完成服务报告成为了一个亟待解决的问题。AI智能工牌的出…

从零到精通:亚马逊和Target自养号测评的下单支付与fang关联技巧

在跨境电商的广阔领域里&#xff0c;自养号测评策略已崛起为众多卖家实现低成本、高效市场推广的一把利器。然而&#xff0c;要驾驭好这一策略&#xff0c;确保其成功实施&#xff0c;关键在于精准把握并满足一系列核心条件。接下来&#xff0c;我们将深入剖析这些条件&#xf…

IEEE投稿模板翻译

>将这一行替换为您的稿件id号(双击此处编辑)< IEEE 期刊和会议论文的撰写准备&#xff08;2022&#xff09; 第一作者 A. 作者&#xff0c;IEEE成员&#xff0c;第二作者 B. 作者&#xff0c;第三作者 C. 作者 Jr.&#xff0c;IEEE成员 摘要—本文档为IEEE会刊、期刊和…

《Neon程序员指南》文档中关于矩阵转置的两处笔误

今天在看《Neon程序员指南》&#xff08;Neon Programmer Guide for Armv8-A Coding for Neon&#xff09;发现两处笔误&#xff0c;随手记在这里。 图6-11中&#xff0c;左边的指令应该是trn1 v1.4s, v0.,4s, v3.4s。 图6-15中trn1的图中有两个箭头画错了。

漏洞挖掘 | 记一次edu通过奖学金名单泄露学号的横向渗透

0x1 前言 哈喽&#xff0c;师傅们&#xff01; 这篇文章主要是给师傅们分享下一个简单的手法&#xff0c;通过打edu的时候&#xff0c;我们可以在一些学生管理登录后台&#xff0c;需要我们使用账号是学号登录的系统&#xff0c;然后我们可以尝试通过去网上找公开的奖学金名单…

JAVA在线教育新利器高效答题系统小程序源码

在线教育新利器——高效答题系统 &#x1f680;【开篇引入&#xff1a;教育新风尚】&#x1f680; 在这个快节奏的时代&#xff0c;学习不再局限于教室的四角&#xff0c;在线教育如雨后春笋般蓬勃发展。而今天&#xff0c;我要给大家揭秘一款在线教育的新宠儿——高效答题系…

传统CV算法——threshold阈值算法介绍

阈值化函数我的理解为&#xff0c;在计算机图像视觉中&#xff0c;我们常见的RGB图像表现的信息过多&#xff0c;可能会存在于一些掺杂的噪声&#xff08;因为针对视觉目标不是我们需要的&#xff09;&#xff0c;因此使用阈值算法&#xff0c;直接效果就是可以降噪&#xff0c…

微信公众号获取 openid: 从零到一快速实现一个微信公众号授权项目

一. 前言 上一篇文章说到&#xff0c;微信官方团队发了一则公告&#xff0c;美其名曰&#xff1a;“为了优化开发体验&#xff0c;避免多个同一功能接口对开发者造成困扰&#xff0c;微信团队将对下发统一消息接口进行如下调整。” 正是由于这个调整&#xff0c;而将部分开发者…

力扣: 快乐数

文章目录 需求分析代码结尾 需求 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。 然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。 …

社群空间站9.9付费入群系统二开源码 易支付版全套搭建教程

1.创建站点 2.搭建环境 php7.2 3.上传源码包 数据库批量修改sq9.dongge1.icu s10.dongge1.icu 改为你的域名 4.上传数据库 修改数据库文件/data/config/ 5.访问域名 6.账户密码 admin 123456 7.易支付修改地址是在/data/tpl/app/default/yy_shequn2/lib/epay.config.php…

【动捕_VRPN_ROS2】安装vrpn_client_ros2库将动捕数据转换ROS2话题

安装vrpn_client_ros2库将动捕数据转换ROS2话题 环境&#xff1a; Ubuntu &#xff1a;20.04 LTS ROS &#xff1a;ROS2 Foxy 安装VRPN库 执行以下命令安装VRPN库。 git clone https://github.com/vrpn/vrpn.git mkdir -p vrpn/build cd vrpn/build cmake .. make sudo …