事务并发控制之说透mvcc

在这里插入图片描述

前言

不知道有没有人有过这样的想法💡,为什么在MySQL中已经有了各种各样的锁了,还需要mvcc呢?如果你没有想过这个问题,那只能证明你真的没有想过。

但是我的建议是可以去想一下,如果你从来没有想过这个问题的话,因为这个问题还挺重要的!思考清楚这个问题,将更有利于我们深入了解MySQL!

接下来我将通过这篇文章向你讲述MVCC中各个方面的知识,保证让你一次看的爽,看的明白!从而更好的理解我开头提出来的问题!

扫盲

MVCC (Multi-Version Concurrency Control) 是一种数据库并发控制的技术。它用于解决多个事务同时访问数据库时可能发生的数据不一致性和并发冲突的问题。
具体来说就是:MVCC意图解决读写锁造成的多个、长时间的读操作饿死写操作问题。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样
在做查询的时候就不用等待另一个事务释放锁。

举个列子,现在有两个事务(可以理解为两个客户端)

事务一:

BEGIN 

SELECT * FROM `subject`  WHERE id=1
  .....(此处省略一万行)
SELECT * FROM `subject`  WHERE id=999999

COMMIT;

事务二:

BEGIN 

UPDATE `subject` SET `name`='ccc121121' WHERE id =1
 
COMMIT;

事务一有很多个查询需要做,而事务二只有一个更新需要做,假设事务一需要耗费很长时间,如果一定要等到第一个事务执行完成以后才能进行第二个事务的话,那用户的体验将会非常差,所以为了解决个问题才
提出了mvcc,mvcc的存在使得 ** 你可以读我可以写!**

事务到底是啥?

事务是一个或多个 SQL 语句组成的一个执行单元,这些 SQL 语句要么全部执行成功,要么全部不执行,不会出现部分执行的情况。 事务是数据库管理系统执行过程中的一个逻辑单位,由有限的数据库操作序列构成。事务的主要作用是保证数据库操作的一致性,即事务内的操作,要么全部成功,要么全部失败回滚,不会出现中间状态。这对于维护数据库的完整性和一致性非常重要。

事务具有四个基本特性,也就是通常所说的 ACID 特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

那么这些事务的特性该如何保证呢?

从数据库层面上说,一致性是最终目的,数据库通过原子性、隔离性、持久性来实现数据的一致性。

但是,如果你在事务里故意写出违反约束的代码,一致性还是无法保证的。例如,你在转账的例子中,你的代码里故意不给B账户加钱,那一致性还是无法保证。

因此,还必须从应用层角度考虑

原子性:

Innodb中的undo log可以是实现原子性的关键,当事务回滚时会撤销所有已经执行完毕的sql语句,但是需要记录回滚的日志信息。

例如:

(1)当你delete一条数据的时候,就需要记录这条数据的信息,回滚的时候,insert这条旧数据

(2)当你update一条数据的时候,就需要记录之前的旧值,回滚的时候,根据旧值执行update操作

(3)当你insert一条数据的时候,就需要这条记录的主键,回滚的时候,根据主键执行delete操作

undo log记录了这些回滚需要的信息,当事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。

持久性:

innodb中的redo log可以保证持久性。Mysql是先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再刷回磁盘上。如果此时突然宕机,内存中的数据就会丢失。

redo log解决上面的问题。当做数据修改的时候,不仅在内存中操作,还会在redo log中记录这次操作。当事务提交的时候,会将redo log日志进行刷盘(redo log一部分在内存中,一部分在磁盘上)。当数据库宕机重启的时候,会将redo log中的内容恢复到数据库中,再根据undo log和bin log内容决定回滚数据还是提交数据。

隔离性:

Mysql利用锁和MVCC多版本并发控制(Multi Version Concurrency Control)来保证隔离性。一个行记录数据有多个版本对快照数据,这些快照数据在undo log中。

如果一个事务读取的行正在做DELELE或者UPDATE操作,读取操作不会等行上的锁释放,而是读取该行的快照版本

但是有一点说明一下,在事务隔离级别为读已提交(Read Commited)时,一个事务能够读到另一个事务已经提交的数据,是不满足隔离性的。但是当事务隔离级别为可重复读(Repeateable Read)中,是满足隔离性的。

说到隔离性我们不得不说一下事务的隔离级别

事务的隔离级别与并发问题

事务的并发问题有哪些

先看一下访问相同数据的事务在不保证串行执行 (也
就是执行完一个再执行另一个)的情况下可能会出现哪些问题:

  1. 脏写( Dirty Write )
    对于两个事务 Session A、Session B,如果事务Session A 修改了 另一个 未提交 事务Session B 修改过 的数
    据,那就意味着发生了 脏写

  2. 脏读( Dirty Read )
    对于两个事务 Session A、Session B,Session A读取了已经被 Session B更新但还没有被提交的字段。
    之后若 Session B 回滚 ,Session A 读取的内容就是临时且无效的。
    Session A和Session B各开启了一个事务,Session B中的事务先将studentno列为1的记录的name列更新
    为’张三’,然后Session A中的事务再去查询这条studentno为1的记录,如果读到列name的值为’张三’,而
    Session B中的事务稍后进行了回滚,那么Session A中的事务相当于读到了一个不存在的数据,这种现象
    就称之为 脏读 。

  3. 不可重复读( Non-Repeatable Read )
    对于两个事务Session A、Session B,Session A 读取 了一个字段,然后 Session B 更新 了该字段。 之后
    Session A 再次读取 同一个字段, 值就不同 了。那就意味着发生了不可重复读。
    我们在Session B中提交了几个 隐式事务 (注意是隐式事务,意味着语句结束事务就提交了),这些事务
    都修改了studentno列为1的记录的列name的值,每次事务提交之后,如果Session A中的事务都可以查看
    到最新的值,这种现象也被称之为 不可重复读 。

  4. 幻读( Phantom )
    对于两个事务Session A、Session B, Session A 从一个表中 读取 了一个字段, 然后 Session B 在该表中 插
    入 了一些新的行。 之后, 如果 Session A 再次读取 同一个表, 就会多出几行。那就意味着发生了幻读。
    Session A中的事务先根据条件 studentno > 0这个条件查询表student,得到了name列值为’张三’的记录;
    之后Session B中提交了一个 隐式事务 ,该事务向表student中插入了一条新记录;之后Session A中的事务
    再根据相同的条件 studentno > 0查询表student,得到的结果集中包含Session B中的事务新插入的那条记
    录,这种现象也被称之为 幻读 。我们把新插入的那些记录称之为 幻影记录 。

SQL中的四种隔离级别

为了解决上述存在的并发问题,相应的SQL中也给出了四种对应隔离级别:

  • READUNCOMMITTED:读未提交,在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。不能避免脏读、不可重复读、幻读。
  • READCOMMITTED:读已提交,它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。可以避免脏读,但不可重复读、幻读问题仍然存在。
  • REPEATABLEREAD:可重复读,事务A在读到一条数据之后,此时事务B对该数据进行了修改并提交,那么事务A再读该数据,读到的还是原来的内容。可以避免脏读、不可重复读,但幻读问题仍然存在。这是MySQL的默认隔离级别。
  • SERIALIZABLE:可串行化,确保事务可以从一个表中读取相同的行。在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。所有的并发问题都可以避免,但性能十分低下。能避免脏读、不可重复读和幻读。

无论师并发问题,还是四种隔离级别他们都是因为事务的并发操作而产生的,说到这里我们得先说一下什么数据库事务的并发操作有!

MySQL并发事务访问相同记录

并发事务访问相同记录的情况大致可以划分为3种

  • 读-读
  • 写-写
  • 读-写

读-读 情况,即并发事务相继读取相同的记录。读取操作本身不会对记录有任何影响,并不会引起什么
问题,所以允许这种情况的发生。

写-写 情况,即并发事务相继对相同的记录做出改动。在这种情况下会发生 脏写 的问题,任何一种隔离级别都不允许这种问题的发生。所以在多个未提交事务
相继对一条记录做改动时,需要让它们 排队执行 ,这个排队的过程其实是通过 锁 来实现的。

读-写 或 写-读 ,即一个事务进行读取操作,另一个进行改动操作。这种情况下可能发生 脏读 、不可重
复读 、 幻读 的问题。

我们现在也知道读-写这种并发操作会带来很多的问题,出问题就要解决问题呀,对吧!活着的意义就是不断发现问题然后解决问题,最后死去!
在解决问题之前我们要先了解一下什么是"当前读"什么又是"快照读"

什么是当前读和快照读?

当前读它读取的数据库记录,都是当前最新的版本,会对当前读取的数据进行加锁,防止其他事务修改数据。是悲观锁的一种操作。如下操作都是当前读:

  • selectlockin share mode(共享锁)
  • select forupdate (排他锁)
  • update (排他锁)
  • insert (排他锁)
  • delete (排他锁)

如果不了解锁相关的东西可以看我之前写的一篇《【面试题】细说mysql中的各种锁》的文章,里面讲的很详细!

快照读的实现是基于多版本并发控制,即MVCC,既然是多版本,那么快照读读到的数据不一定是当前最新的数据,有可能是之前历史版本的数据。
如下操作是快照读:

  • 不加锁的select操作(注:事务级别不是串行化,也就是平时经常使select查询语句)

所以有没有发现其实快照读就是体现mvcc的一种方式!

所以到现在我们可以回答开篇的问题了,锁和mvcc是共存的,运用的场景不同,目的就是为了提高数据的并发性!

MVCC实现原理

MVCC的实现依赖于:隐藏字段、Undo Log、Read View。

什么是隐藏列?

对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列。

  • trx_id :每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的 事务id 赋值给trx_id 隐藏列。
  • roll_pointer :每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undo日志 中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。

undo log

前面我们已经提到了,Mysql利用锁和MVCC多版本并发控制(Multi Version Concurrency Control)来保证隔离性。一个行记录数据有多个版本对快照数据,这些快照数据在undo log中。

什么是Read View呢

直接翻译就是:读视图,在多个事务,不同隔离级别下数据有多个版本,那在不同的事务中该如何展示呢?
Read View就是为了解决这一问题!

使用 READ UNCOMMITTED 隔离级别的事务,由于可以读到未提交事务修改过的记录,所以直接读取记录
的最新版本就好了。
使用 SERIALIZABLE 隔离级别的事务,InnoDB规定使用加锁的方式来访问记录。
使用 READ COMMITTED 和 REPEATABLE READ 隔离级别的事务,都必须保证读到 已经提交了的 事务修改
过的记录。假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,核心问
题就是需要判断一下版本链中的哪个版本是当前事务可见的,这是ReadView要解决的主要问题。

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

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

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

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

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

  3. 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。

ReadView的规则

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

如果被访问版本的trx_id属性值与ReadView中的 creator_trx_id 值相同,意味着当前事务在访问
它自己修改过的记录,所以该版本可以被当前事务访问。

如果被访问版本的trx_id属性值小于ReadView中的 up_limit_id 值,表明生成该版本的事务在当前
事务生成ReadView前已经提交,所以该版本可以被当前事务访问。

如果被访问版本的trx_id属性值大于或等于ReadView中的 low_limit_id 值,表明生成该版本的事
务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。

如果被访问版本的trx_id属性值在ReadView的 up_limit_id 和 low_limit_id 之间,那就需要判
断一下trx_id属性值是不是在 trx_ids 列表中。

如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问。

如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。

MVCC整体操作流程

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

虚拟机扩容方法

概述 我的虚拟机开始的内存是40G,接下来要扩成60GB 扩容步骤 步骤1 步骤2 步骤3 修改扩容后的磁盘大小,修改后的值只可以比原来的大,修改完成后点击扩展,等待扩展完成 步骤4 虽然外面扩展成功,但是新增的磁盘空间虚拟机内部还…

自动化测试的7个步骤

🔥 交流讨论:欢迎加入我们一起学习! 🔥 资源分享:耗时200小时精选的「软件测试」资料包 🔥 教程推荐:火遍全网的《软件测试》教程 📢欢迎点赞 👍 收藏 ⭐留言 &#x1…

阿里巴巴Java开发规范——编程规约(3)

# 阿里巴巴Java开发规范——编程规约(3) 编程规约 (四) OOP规约 1.【强制】构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中 这条编程规范的目的是为了保持代码的清晰性、可读性…

怎么理解算力?1000P算力是什么概念?

算力,指计算机系统在单位时间内能够完成的计算任务量,它涵盖了CPU、GPU、TPU等硬件,每秒能处理的数据量,通常以“P”(PetaFLOPS,即千万亿次浮点运算每秒)为单位来衡量,是评估计算机性…

【笔试强训】day8

没啥好说&#xff0c;都是一遍过 1.求最小公倍数 思路&#xff1a; 求lcm。其实就是两数之乘积除以两个数的gcd。gcd就是是求两个数的最大公约数。 代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std;int gcd(int a, int …

虚拟线程的定义及使用

0.前言 长期以来&#xff0c;虚拟线程是 Java 中最重要的创新之一。 它们是在 Project Loom 中开发的&#xff0c;自 Java 19 作为预览功能以来一直包含在 JDK 中&#xff0c;自 Java 21 作为最终版本 (JEP 444) 以来&#xff0c;它们已包含在 JDK 中。 1.虚拟线程的作用 任…

【C++】双指针算法:复写零

1.题目 别看这是一道简单题&#xff0c;它的通过率低于一些中等甚至困难的题目&#xff01; 大大增加这道题目难度的是最后一句话&#xff1a;1.不可越界写入。2.就地修改。 如果可以再创建一个数组的话&#xff0c;那么这道题目就会非常简单&#xff0c;但这道题目必须要求在…

网络工程师---第十天

ARP表&#xff1a; 提起ARP表必然先想起ARP&#xff08;address resolution protocol&#xff09;协议&#xff0c;地址解析协议。 在实际应用中&#xff0c;我们经常遇到这样的问题&#xff1a;已知一个机器的IP地址&#xff0c;但在实际网络的链路上传送数据帧时&#xff0c;…

Redis安装部署教程

文章目录 Redis安装部署 Redis安装部署 &#xff08;1&#xff09; 下载xx.msi安装包&#xff0c;可以通过该下载地址进行下载。 &#xff08;2&#xff09; 双击下载的安装包进行安装。 &#xff08;3&#xff09; 选择安装目录&#xff08;默认C盘&#xff09;&#xff…

Kali Linux扩容(使用图形化界面)

因为今天在拉取vulhub中的镜像的时候报错空间不够&#xff0c;因为最开始只给了20GB的空间&#xff0c;所以现在需要扩容了&#xff0c;结合了一下网上的找到了简便的解决方法 1.首先虚拟机设置->磁盘->扩展 小插曲&#xff1a;在对虚拟机磁盘进行扩容以后&#xff0c;…

Flask-SQLAlchemy 中使用显式主主数据库设置

1、问题背景 在一个 Flask-SQLAlchemy 项目中&#xff0c;用户想要使用显式主主数据库设置。具体而言&#xff0c;他想要能够从默认数据库中读取数据&#xff0c;并将数据持久化到两个主数据库中。他希望知道是否可以使用 Flask-SQLAlchemy 和 binds 来实现这一目标。 2、解决…

解决宝塔面板无法访问(无法访问或拒绝链接)

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;Linux ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 问题如下&#xff1a; 本人设置了授权IP&#xff0c;但是有些问题&#xff0c;所以是打算取消授权IP 重…

使用QQ邮箱进行登录验证

使用场景不多说&#xff0c;接下来直接看实现~ 登录到QQ邮箱&#xff0c;进入设置 打开IMAP/SMTP服务&#xff0c;记得把授权码记录下来&#xff0c;后面配置文件中需要用到 新建application的配置文件 spring:mail:# 指定邮件服务器地址host: smtp.qq.comusername: 你自己的q…

分布式与一致性协议之拜占庭将军问题(一)

拜占庭将军问题 概述 拜占庭将军问题其实是借拜占庭将军故事展现了分布式共识问题&#xff0c;探讨和论证了解决的办法。实际上&#xff0c;拜占庭将军问题是分布式领域最复杂的一个容错模型&#xff0c;一旦搞懂了它&#xff0c;久能掌握分布式共识问题的解决思路&#xff0…

NTLM认证

文章目录 1.概念(1) 本地认证(2) SAM(3) NTLM Hash(4) NTLM 和 NTLM Hash(5) NTLM v2 1.概念 (1) 本地认证 Windows不存储用户的明文密码&#xff0c;它会将用户的明文密码经过加密后存储在 SAM (Security Account Manager Database&#xff0c;安全账号管理数据库)中。 (2)…

Marching Cubes算法

Marching Cubes算法 1. 简介2. 算法原理的理解2.1 如何找到面经过的这些小块(六面体)&#xff1f;2.2 找到后&#xff0c;如何又进一步的找到面与这些小块(六面体)的交点&#xff1b;2.3 这些交点按照怎么的拓扑连接关系连接&#xff0c;是怎么操作的&#xff1f; 3. 总结4. 参…

【链表】Leetcode 两数相加

题目讲解 2. 两数相加 算法讲解 我们这里设置一个头结点&#xff0c;然后遍历两个链表&#xff0c;使用一个flag记录相加的结果和进位&#xff0c;如果两个链表没有走到最后或者进位不等于0&#xff0c;我们就继续遍历处理进位&#xff1b;如果当前的链表都遍历完成了&#x…

基于springboot实现的摄影跟拍预定管理系统

开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven…

【系统分析师】多媒体技术与知识产权标准化

文章目录 一、多媒体技术1、音频2、图像3、媒体的分类4、有损压缩和无损压缩5、多媒体标准 二、知识产权标准化1、保护范围和对象2、保护期限3、知识产权人确定4、侵权判定5、标准化 一、多媒体技术 1、音频 # 声音文件格式 .wav .mp3 .ra .mid .snd .au .aif .voc2、图像 # 彩…

一招下载transformers真不用网上那些教程(我试了1*mol多次才知道)

pip很多是2 然而&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;…