【MySQL事务篇】多版本并发控制(MVCC)

news2024/11/26 20:38:42

多版本并发控制(MVCC)

文章目录

  • 多版本并发控制(MVCC)
    • 1. 概述
    • 2. 快照读与当前读
      • 2.1 快照读
      • 2.2 当前读
    • 3. MVCC实现原理之ReadView
      • 3.1 ReadView概述
      • 3.2 设计思路
      • 3.3 ReadView的规则
      • 3.4 MVCC整体操作流程
    • 4. 举例说明
      • 4.1 READ COMMITTED隔离级别下
      • 4.2 REPEATABLE READ隔离级别下
    • 5. 总结

1. 概述

MVCC (Multiversion Concurrency Control),多版本并发控制。顾名思义,MVCC 是通过数据行的多个版本管理来实现数据库的 并发控制 。这项技术使得在InnoDB的事务隔离级别下执行 一致性读 操作有了保证。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样在做查询的时候就不用等待另一个事务释放锁。

2. 快照读与当前读

MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理 读-写冲突 ,做到即使有读写冲突时,也能做到 不加锁 , 非阻塞并发读 ,而这个读指的就是 快照读 , 而非 当前读 。当前读实际上是一种加锁的操作,是悲观锁的实现。而MVCC本质是采用乐观锁思想的一种方式。

2.1 快照读

快照读又叫一致性读,读取的是快照数据。不加锁的简单的 SELECT 都属于快照读,即不加锁的非阻塞读;比如这样:

SELECT * FROM player WHERE ...

快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读。

2.2 当前读

当前读读取的是记录的最新版本(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。加锁的 SELECT,或者对数据进行增删改都会进行当前读。比如:

SELECT * FROM student LOCK IN SHARE MODE; # 共享锁
SELECT * FROM student FOR UPDATE; # 排他锁
INSERT INTO student values ... # 排他锁
DELETE FROM student WHERE ... # 排他锁
UPDATE student SET ... # 排他锁

3. MVCC实现原理之ReadView

MVCC 的实现依赖于:隐藏字段Undo LogRead View

3.1 ReadView概述

在MVCC 机制中,多个事务对同一个行记录进行更新会产生多个历史快照,这些历史快照保存在 Undo Log 里。如果一个事务想要查询这个行记录,需要读取哪个版本的行记录呢? 这时就需要用到 ReadView 了,它帮我们解决了行的可见性问题。

ReadView 就是事务A在使用MVCC机制进行快照读操作时产生的读视图。当事务启动时,会生成数据库系统当前的一个快照,InnoDB 为每个事务构造了一个数组,用来记录并维护系统当前 活跃事务 的ID(“活跃”指的就是,启动了但还没提交)。

3.2 设计思路

使用 READ UNCOMMITTED 隔离级别的事务,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好了。

使用 SERIALIZABLE 隔离级别的事务,InnoDB规定使用加锁的方式来访问记录。

使用 READ COMMITTEDREPEATABLE READ 隔离级别的事务,都必须保证读到 已经提交了的 事务修改过的记录。假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,核心问题就是需要判断版本链中的哪个版本是当前事务可见的,这是ReadView要解决的主要问题。

ReadView中主要包含4个比较重要的内容,分别如下:

  1. creator_trx_id ,创建这个 Read View 的事务 ID

    只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为事务分配事务id,否则在一个只读事务中的事务id值都默认为0。

  2. trx_ids ,表示在生成ReadView时当前系统中活跃的读写事务的 事务id列表

  3. up_limit_id ,活跃的事务中最小的事务 ID

  4. low_limit_id,表示生成ReadView时系统中应该分配给下一个事务的 id 值。low_limit_id 是系统最大的事务id值,这里要注意是系统中的事务id,需要区别于正在活跃的事务ID

    low_limit_id并不是trx_ids中的最大值,事务id是递增分配的。比如,现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时,trx_ids就包括1和2,up_limit_id的值就是1,low_limit_id的值就是4

3.3 ReadView的规则

有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见。

  1. 如果被访问版本的trx_id属性值与ReadView中的 creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
  2. 如果被访问版本的trx_id属性值小于ReadView中的 up_limit_id 值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
  3. 如果被访问版本的trx_id属性值大于或等于ReadView中的 low_limit_id 值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
  4. 如果被访问版本的trx_id属性值在ReadView的 up_limit_id 和 low_limit_id 之间,那就需要判断一下trx_id属性值是不是在 trx_ids 列表中。
  • 如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问。
  • 如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问

3.4 MVCC整体操作流程

下当查询一条记录的时候,系统如何通过MVCC找到它:

  1. 首先获取事务自己的版本号,也就是事务 ID;
  2. 获取 ReadView;
  3. 查询得到的数据,然后与 ReadView 中的事务版本号进行比较;
  4. 如果不符合 ReadView 规则,就需要从 Undo Log 中获取历史快照;
  5. 最后返回符合规则的数据。

如果版本链中的最后一个版本也不可见,则该条记录对该事务完全不可见,查询结果就不包含该记录。

在隔离级别为 读已提交(Read Committed)时,一个事务中的每一次 SELECT 查询都会重新获取一次 Read View。

事务说明
begin;
select * from student where id >2;获取一次Read View
select * from student where id >2;获取一次Read View
commit;

此时同样的查询语句都会重新获取一次 Read View,这时如果 Read View 不同,就可能产生不可重复读或者幻读的情况。

当隔离级别为 可重复读 的时候,就避免了不可重复读,这是因为一个事务只在第一次 SELECT 的时候会获取一次 Read View,而后面所有的 SELECT 都会复用这个 Read View,如下表所示:

在这里插入图片描述

4. 举例说明

4.1 READ COMMITTED隔离级别下

READ COMMITTED :每次读取数据前都生成一个ReadView。

现在有两个 事务id 分别为 1020 的事务在执行:

# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...

此刻,表student 中 id1 的记录得到的版本链表如下所示:

在这里插入图片描述

假设现在有一个使用 READ COMMITTED 隔离级别的事务开始执行:

# 使用READ COMMITTED隔离级别的事务
BEGIN;
# SELECT1:Transaction 10、20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'张三'

之后,我们把 事务id10 的事务提交一下:

# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
COMMIT;

然后再到 事务id20 的事务中更新一下表 student 中 id1 的记录:

# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...
UPDATE student SET name="钱七" WHERE id=1;
UPDATE student SET name="宋八" WHERE id=1;

此刻,表student中 id1 的记录的版本链就长这样:

在这里插入图片描述

然后再到刚才使用 READ COMMITTED 隔离级别的事务中继续查找这个 id 为 1 的记录,如下:

# 使用READ COMMITTED隔离级别的事务
BEGIN;
# SELECT1:Transaction 10、20均未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'张三'
# SELECT2:Transaction 10提交,Transaction 20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'王五'

4.2 REPEATABLE READ隔离级别下

使用 REPEATABLE READ 隔离级别的事务来说,只会在第一次执行查询语句时生成一个 ReadView ,之后的查询就不会重复生成了

比如,系统里有两个 事务id 分别为 1020 的事务在执行:

# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...

此刻,表student 中 id1 的记录得到的版本链表如下所示:

在这里插入图片描述

假设现在有一个使用 REPEATABLE READ 隔离级别的事务开始执行:

# 使用REPEATABLE READ隔离级别的事务
BEGIN;
# SELECT1:Transaction 10、20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'张三'

之后,我们把 事务id10 的事务提交一下,就像这样:

# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;
COMMIT;

然后再到 事务id20 的事务中更新一下表 student 中 id1 的记录:

# Transaction 20
BEGIN;
# 更新了一些别的表的记录
...
UPDATE student SET name="钱七" WHERE id=1;
UPDATE student SET name="宋八" WHERE id=1;

此刻,表student 中 id1 的记录的版本链长这样:

在这里插入图片描述

然后再到刚才使用 REPEATABLE READ 隔离级别的事务中继续查找这个 id1 的记录,如下:

# 使用REPEATABLE READ隔离级别的事务
BEGIN;
# SELECT1:Transaction 10、20均未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值为'张三'
# SELECT2:Transaction 10提交,Transaction 20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值仍为'张三'

5. 总结

这里介绍了 MVCCREAD COMMITTDREPEATABLE READ 这两种隔离级别的事务在执行快照读操作时访问记录的版本链的过程。这样使不同事务的 读-写写-读 操作并发执行,从而提升系统性能。

核心点在于 ReadView 的原理, READ COMMITTDREPEATABLE READ 这两个隔离级别的一个很大不同就是生成ReadView的时机不同:

  • READ COMMITTD 在每一次进行普通SELECT操作前都会生成一个ReadView
  • REPEATABLE READ 只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个ReadView

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

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

相关文章

viewBinding与反射结合的实用实践

首先,官方教程指个路:视图绑定 本文阅读认真阅读大约需要5-20分钟 也可直接跳到文末3.0看最终方案 目录 1 关于ViewBinding第一点,命名符合一定的规则第二点,继承自ViewBinding 2 正常使用不便之处3 话不多说直接看代码1.04 话不…

数据结构与算法之美学习笔记:16 | 二分查找(下):如何快速定位IP对应的省份地址?

目录 前言二分查找的变形问题变体一:查找第一个值等于给定值的元素变体二:查找最后一个值等于给定值的元素变体三:查找第一个大于等于给定值的元素变体四:查找最后一个小于等于给定值的元素 解答开篇内容小结 前言 本节课程思维导…

vue3+ts 项目遇到的问题和bug

1.router中使用pinia报错 pinia.mjs:1709 Uncaught Error: [🍍]: "getActivePinia()" was called but there was no active Pinia. Are you trying to use a store before calling "app.use(pinia)"? See https://pinia.vuejs.org/core-concep…

[科研图像处理]用matlab平替image-j,有点麻烦,但很灵活!

做材料与生物相关方向的同学应该对image-j并不陌生,前几天有个师兄拜托我用image-j分析一些图片,但使用过后发现我由于不了解image-j的工作流程而对结果并不确信,而且image-j的功能无法拓展,对有些图片的处理效果并不好&#xff0…

【网络协议】聊聊HTTPDNS如何工作的

传统 DNS 存在哪些问题? 域名缓存问题 我们知道CND会进行域名解析,但是由于本地会进行缓存对应的域名-ip地址,所以可能出现过期数据的情况。 域名转发问题 出口 NAT 问题 域名更新问题 解析延迟问题 因为在解析DNS的时候,需要进行…

【C语言】函数的系统化精讲(二)

🌈write in front :🔍个人主页 : 啊森要自信的主页 🌈作者寄语 🌈: 小菜鸟的力量不在于它的体型,而在于它内心的勇气和无限的潜能,只要你有决心,就没有什么事情是不可能的…

vue3错误排查-POST请求的body参数 传参方式form-data和json

问题:vue3实现登录功能,登录成功后 跳转到登陆后的界面 一秒后 闪退回登录页 对应的输出结果也一闪而过,反复复查了代码,没问题。 自测:进行断点输出调试。强行跳转到登陆后的界面,查看输出的结果。 没有报…

安全操作(安卓推流)程序

★ 安全操作项目 项目描述:安全操作项目旨在提高医疗设备的安全性,特别是在医生离开操作屏幕时,以减少非授权人员的误操作风险。为实现这一目标,我们采用多层次的保护措施,包括人脸识别、姿势检测以及二维码识别等技术…

Linux 实现原理 — NUMA 多核架构中的多线程调度开销与性能优化

前言 NOTE:本文中所指 “线程” 均为可执行调度单元 Kernel Thread。 NUMA 体系结构 NUMA(Non-Uniform Memory Access,非一致性存储器访问)的设计理念是将 CPU 和 Main Memory 进行分区自治(Local NUMA node&#x…

1070 结绳

一.问题: 给定一段一段的绳子,你需要把它们串成一条绳。每次串连的时候,是把两段绳子对折,再如下图所示套接在一起。这样得到的绳子又被当成是另一段绳子,可以再次对折去跟另一段绳子串连。每次串连后,原来…

【网络安全 --- web服务器解析漏洞】IIS,Apache,Nginx中间件常见解析漏洞

一,工具及环境准备 以下都是超详细保姆级安装教程,缺什么安装什么即可(提供镜像工具资源) 1-1 VMware 16.0 安装 【网络安全 --- 工具安装】VMware 16.0 详细安装过程(提供资源)-CSDN博客文章浏览阅读20…

Android Datastore 动态创建与源码解析

涉及到的知识点 1、协程原理---->很好的博客介绍,一个小故事讲明白进程、线程、Kotlin 协程到底啥关系? 2、Channel知识点---->Android—kotlin-Channel超详细讲解 3、Coroutines : CompletableDeferred and structured concurrency 封装的DataS…

动态规划-丑数

** 描述 把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第 n个丑数。 数据范围: 0≤n≤2000 要求&#x…

Mysql数据库 9.SQL语言 查询语句 连接查询、子查询

连接查询 通过查询多张表,用连接查询进行多表联合查询 关键字:inner join 内连接 left join 左连接 right join 右连接 数据准备 创建新的数据库:create database 数据库名; create database db_test2; 使用数据库:use 数据…

企业如何搭建智能客服系统?

在数字化时代,企业面临着客户需求多样化、市场竞争日益激烈等多重挑战。为了更好地满足客户的需求、提供高效的服务,越来越多的企业开始搭建智能客服系统。智能客服系统结合了人工智能和自然语言处理技术,可以实现自动回复、智能推荐以及数据…

Linux内核移植之主频设置

一. Linux内核移植 正点原子 ALPHA开发板已经添加到 Linux内核里面去了,前面文章关于如何添加已经掌握。但是,还有一些驱动的问题需要修改。 正点原子 I.MX6U-ALPHA 开发板所使用的 I.MX6ULL 芯片主频都是 792MHz 的,也就是NXP 官方宣…

MySQL的event的使用方法

MySQL的event的使用方法 一、事件定时策略 1、查看event事件开启状态 SHOW VARIABLES LIKE event_scheduler;如图,Value值 ON:打开,OFF:关闭。 2、设置event事件打开 SET GLOBAL event_scheduler ON;如果MySQL重启了&#x…

Python模块导入出现ModuleNotFoundError: No module named ‘***’解决方法

概述 几年没弄python了,全部还会给老师,今天弄了个demo,老是报错,在此记录下,方便后续查阅。 环境:Windows10 开发IDEA:PyCharm 2023.1.3 1、报错如下所示 2、解决方法:安装execjs…

AJAX-解决回调函数地狱问题

一、同步代码和异步代码 1.同步代码 浏览器是按照我们书写代码的顺序一行一行地执行程序的。浏览器会等待代码的解析和工作,在上一行完成之后才会执行下一行。这也使得它成为一个同步程序。 总结来说:逐行执行,需原地等待结果后&#xff0…

[python 刷题] 437 Path Sum III

[python 刷题] 437 Path Sum III 之前有写过 Path Sum I & II, leetcode 112 & 113,虽然使用 JS 写的,不过 python 的实现也更新了一下 题目如下: Given the root of a binary tree and an integer targetSum, return the number o…