一篇文章搞懂MVCC

news2024/12/25 9:01:44

事务

什么是事务?当事务对数据库进行多个更改时,要么在事务提交时所有更改都成功,要么在事务回滚时所有更改都被撤销。 在 MySQL 中,事务支持是在引擎层实现的。MySQL 是一个支持多引擎的系统,但并不是所有的引擎都支持事务。比如 MySQL 原生的 MyISAM 引擎就不支持事务,这也是 MyISAM 被 InnoDB 取代的重要原因之一。

事务有四个特性,ACID,即原子性、一致性、隔离性和持久性。

开启事务用 start transaction 或者 begin ,配套的提交语句是 commit,回滚语句是 rollback。

//开启事务,READ WRITE可写可不写,默认就是READ WRITE,也可以指定只读READ ONLY
START TRANSACTION READ WRITE;

//事务里面有查询语句,有更新语句
SELECT * FROM `ddk_app_config`;
update ddk_app set desc='aaa' where id=1;

//提交该事务
COMMIT; 

//回滚该事务
ROLLBACK;

一条或者多条sql语句都属于事务,那为什么我们平时在写update语句的时候没有手动开启start transaction呢,因为我们有一个属性叫做autocommit自动提交,这个属性平时就是开着的。我们可以看下会话和全局的都是开着的。

image.png

set SESSION autocommit=0,关闭自动提交,关闭以后单条语句也必须要进行commit或者ROLLBACK。 有些客户端连接框架会默认连接成功后先执行一个 set autocommit=0 的命令。这就导致接下来的查询都在事务中,如果是长连接,就导致了意外的长事务。所以我建议总是使用 set autocommit=1 , 通过显式语句的方式来启动事务。

查询事务表,比如下面这个语句,用于查找持续时间超过 60s 的事务。

select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60

在这张表里面保存的是还没有提交的事务,会有一些关于事务的信息字段,具体的说明见官网:https://dev.mysql.com/doc/refman/8.0/en/information-schema-innodb-trx-table.html
image.png

脏读幻读不可重复读

当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题。

脏读:能读取到其他线程还没有提交的数据,但是这些数据可能是会回滚的。

image.png

不可重复读:在开启事务之后,读取到其他事务进行修改或者删除提交的的数据。
image.png

幻读:在开启事务之后,读到了其他事务新添加的新数据。 官网定义:https://dev.mysql.com/doc/refman/8.0/en/innodb-next-key-locking.html
image.png

注意:上面三个问题脏读肯定是有危害的,因为你读到的数据如果回滚了,你读到的就是无效数据。但是可重复读跟幻读问题,根据不同的业务场景来,大部分的业务场景来讲,我们是能够接受的。

但是无论如何,是不符合我们数据库设计原则中的隔离性原则;事务跟事务之间进行了相互影响,我们应该让数据在一个事务中进行了读取后,在没有提交事务之前,不能被其他事务进行更改。所以接下来我们看下隔离级别。

隔离级别

为了解决脏读幻读不可重复读这些问题,就有了隔离级别的概念。所谓隔离级别是在多个事务同时进行更改和执行查询时,对性能和结果的性能、一致性之间的平衡进行设置。简单一句话,就是多个事务并发的时候,你是去保证性能还是优先保证数据一致性。隔离级别隔离得越严实,效率就会越低。因此很多时候,我们都要在二者之间寻找一个平衡点。

SQL 标准的事务隔离级别包括:读未提交(READ UNCOMMITTED)、读已提交(READ COMMITTED)、可重复读(REPEATABLE READ)和串行化(SERIALIZABLE)。官网地址:https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html

读未提交: 查询语句以非锁定的方式执行,既然非锁定,那么数据还在操作的时候,只要在内存中了,不管有没有提交,所以会导致读取到还没有提交的数据,这些数据也可能是要回滚的,是脏数据。读取到没有提交的数据,就是脏读。

读已提交: 读取已经提交的数据,所以肯定会解决脏读问题。

可重复读: 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。

串行化: 顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

//修改当前会话读未提交
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

//修改当前会话读已提交级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

//修改当前会话RR级别
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

//修改当前会话串行化级别
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; 


//查看全局隔离级别
SHOW GLOBAL VARIABLES LIKE '%isolation%'; 
//查看会话隔离级别
SHOW SESSION VARIABLES LIKE '%isolation%';

下面这张图就是四个隔离级别对三个问题的解决程度,很重要,大家记住。
image.png

MVCC

同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。InnoDB在实现 MVCC 时用到的一致性读视图,即 consistent read view,用于支持RC(Read Committed,读提交)和RR(Repeatable Read,可重复读)隔离级别的实现。

一致性读取意味着InnoDB使用多版本来向查询提供数据库在某个时间点的快照。该查询查看在该时间点之前提交的事务所做的更改,而不查看后来或未提交的事务所做的更改。所以关键在于快照,就是在某些时间点,我创建一个快照,这个快照创建了之后,后续同一个事务的所有读取都是读取的这个快照内容。

在RC隔离级别下,则每次一致性读都会创建一个新的快照。

在RR隔离级别下,则在第一次一致性读的时候,创建快照。如果不在第一个select创建快照,也可以用 start transaction with consistent snapshot, 这个在事务开启的时候就会创建一个read view。

这里的快照其实不是真正意义的将数据存储了一份,而是一个readView的数据结构保存了某些信息,然后通过对这些信息的判断来达到不会读到最新数据的目的。

image.png

对于 read-view A,要得到 1,就必须将当前值依次执行图中所有的回滚操作得到。

Read View

下面我们来看下什么是Read View,源码在 \storage\innobase\include\read0types.h

class ReadView {

//省略

..................

/** The read should not see any transaction with trx id >= this value. In other words, this is the "high water mark". */

trx_id_t m_low_limit_id; //如果大于等于这个值的事务不可见,也称高水位线



/** The read should see all trx ids which are strictly smaller (<) than this value. In other words, this is the low water mark". */

trx_id_t m_up_limit_id; // 所有小于这个值的事务的值都可见,也称低水位线,其实是m_ids里面的最小值


/** trx id of creating transaction, set to TRX_ID_MAX for free views. */

trx_id_t m_creator_trx_id; // 当前的事务ID


/** Set of RW transactions that was active when this snapshot was taken */

ids_t m_ids; //存活的事务ID,就是在创建readView 没有提交的事务的ID集合

}

m_low_limit_id: 当前系统里面已经创建过的事务 ID 的最大值加 1,记为高水位

m_up_limit_id: 所有存活的(没有提交的)事务ID中最小值,即低水位

m_creator_trx_id:创建这个readView的事务ID

m_ids: 创建readView时,所有存活的事务ID列表,活跃的意思是启动了还没有提交

一个ReadView还不能够判断可见与不可见,可见与不可见跟行数据相关,所以还跟每行数据的一些隐藏字段有关:

DB_ROW_ID: 隐藏的字段ID,当没有主键或者非空唯一索引时,我们的主键所以基于这个递增字段建立。

DB_TRX_ID: 更新这行数据的事务ID

DB_ROLL_PTR : 回滚指针,被改动前的undolog日志指针。

每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把transaction id赋值给这个数据版本的事务ID,记为row trx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。也就是说,数据表中的一行记录,其实可能有多个版本 (row),每个版本有自己的 row trx_id。

下图就是一个数据版本:
image.png

那么ReadView怎么跟我的行数据的DB_TRX_ID来配合做到解决我的不可重复读或者幻读问题呢?

判断规则:

  1. 如果数据的DB_TRX_ID < m_up_limit_id, 都小于存活的事务ID了,那么肯定不存活了,说明在创建ReadView的时候已经提交了,可见。

  2. 如果数据的DB_TRX_ID >=m_low_limit_id, 大于等于我即将分配的事务ID, 那么表明修改这条数据的事务是在创建了ReadView之后开启的,不可见。

  3. 如果 m_up_limit_id<= DB_TRX_ID< m_low_limit_id, 表明修改这条数据的事务在第一次快照之前就创建好了,但是不确定提没提交,判断有没有提交,直接可以根据活跃的事务列表 m_ids判断

    1. DB_TRX_ID如果在m_ids中,表面在创建ReadView之时还没提交,不可见
    2. DB_TRX_ID如果不在m_ids,表面在创建ReadView之时已经提交,可见

这三条规则不用背,用到的时候拿出来对比即可。

案例分析

RR级别,这个是每次查询read view都是一样的
MVCC2.jpg

我们看B事务第一条查询语句,对于id为8的数据DB_TRX_ID=100,符合3.1的条件,DB_TRX_ID如果在m_ids中,表面在创建ReadView之时还没提交,不可见。那么在第二次查询的时候其实也是不可见的,所以解决了幻读的问题

我们看B事务第一条查询语句,对于id为2的数据DB_TRX_ID=102,符合2的条件,表明修改这条数据的事务是在创建了ReadView之后开启的,不可见。那么在第二次查询的时候其实也是不可见的,所以解决了不可重复读的问题

而RC级别下每次select都会生成新的read view。
image.png

我们看B事务第一条查询语句,对于id为8的数据DB_TRX_ID=100,符合3.1的条件,DB_TRX_ID如果在m_ids中,表面在创建ReadView之时还没提交,不可见。那么在第二次查询的时候100<101,满足DB_TRX_ID < m_up_limit_id,此时数据是可见的,造成了幻读

我们看B事务第一条查询语句,对于id为2的数据DB_TRX_ID=102,符合2的条件,表明修改这条数据的事务是在创建了ReadView之后开启的,不可见。那么在第二次查询的时候101<=102<103,符合3.2的条件,DB_TRX_ID如果不在m_ids,表面在创建ReadView之时已经提交,可见,造成了不可重复读

所以MVCC在RC跟RR的区别就在于是不是每次快照读是否都会生成新的read view,这也是为什么RC没有解决幻读跟可重复读。

但是也有个例外:更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。因为如果我在改动数据的时候,拿到的不是最新数据,那么就会导致数据丢失。

UndoLog

undoLog,也就是回滚日志,简单的说,如果是添加数据,回滚把这条记录删除;如果是删除数据,回滚的时候,把删除的重新插入;如果是修改数据,那么把旧值记录下来,然后回滚到以前的值。在《一条更新SQL语句是如何执行的?》 一文中我简单介绍过undolog,感兴趣的小伙伴再复习下。

那么undoLog,有两个作用,一个是回滚的时候,能找到之前相关的数据。比如,我们rollback或者异常中断的时候,能找到改动之前的数据进行恢复。第二个就是在mvcc中也需要用到undolog来找到以往的数据来解决不可重复读跟幻读问题。

在上面案例中如果对某行数据不可见的话,那就通过undoLog回滚指针来找到上一个版本的数据,直到找到可见的数据,通过回滚指针DB_ROLL_PTR来查找上一个版本的事务id,然后通过规则做比较。

最后感谢大家的收看~

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

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

相关文章

详解反向迭代器适配器

目录 一、基本介绍 二、模拟实现 2.1 - operator* 2.2 - vector 和 list 的反向迭代器 一、基本介绍 反向迭代器适配器&#xff08;reverse_iterator&#xff09;&#xff0c;可简称为反向迭代器或逆向迭代器&#xff0c;常用来对容器进行反向遍历。 反向迭代器底层只能选…

Qt安卓开发经验技巧总结V202308

01&#xff1a;01-05 pro中引入安卓拓展模块 QT androidextras 。pro中指定安卓打包目录 ANDROID_PACKAGE_SOURCE_DIR $$PWD/android 指定引入安卓特定目录比如程序图标、变量、颜色、java代码文件、jar库文件等。 AndroidManifest.xml 每个程序唯一的一个全局配置文件&…

史上最简洁实用人工神经元网络c++编写202301

这是史上最简单、清晰…… C语言编写的 带正向传播、反向传播(Forward ……和Back Propagation&#xff09;……任意Nodes数的人工神经元神经网络……。 大一学生、甚至中学生可以读懂。 适合于&#xff0c;没学过高数的程序员……照猫画虎编写人工智能、深度学习之神经网络……

Android OpenCV(七十五): 看看刚”转正“的条形码识别

前言 2021年,我们写过一篇《OpenCV 条码识别 Android 平台实践》,当时的条形码识别模块位于 opencv_contrib 仓库,但是 OpenCV 4.8.0 版本开始, 条形码识别模块已移动到 OpenCV 主仓库,至此我们无需自行编译即可轻松地调用条形码识别能力。 Bar code detector and decoder…

Perl兼容正则表达式函数-PHP8知识详解

在php8中有两类正则表达式函数&#xff0c;一类是perl兼容正则表达式函数&#xff0c;另一类是posix扩展正则表达式函数。二者区别不大&#xff0c;我们推荐使用Perl兼容正则表达式函数。 1、使用正则表达式对字符串进行匹配 用正则表达式对目标字符串进行匹配是正则表达式的主…

59.C++ string容器

目录 1.1string容器的基本概念 1.2string构造函数 1.3string赋值操作 1.4string字符串拼接 1.5string查找和替换 1.6string字符串比较 1.7string字符串存取 1.8string字符串插入和删除 1.9string子串 1.1string容器的基本概念 本质&#xff1a; string是C风格的字…

五家项目进度管理工具,哪家好?

项目进度管理十分依赖项目经理对于项目信息的掌握程度&#xff0c;数字化工具可以很好的解决项目信息不统一的问题。一款好用的项目进度十分重要。目前市面上项目进度管理工具哪家好&#xff1f; 1、Zoho Projects&#xff1b;2、Microsoft Project&#xff1b;3、Trello&#…

解决charles无法抓取localhost数据包

我们有时候在本地调试的时候&#xff0c;使用charles抓取向本地服务发送的请求的&#xff0c;发现无法抓取。 charles官方也作了相应说明&#xff1a; 大概意思就是 某些系统使用的是硬编码不能使用localhost进行传输&#xff0c;所以当我们连接到 localhost的时候&#xff0c…

(查看,和保存)windows下通过cmd命令符窗口查看、保存文件目录结构

背景 有时候我们查看目录结构&#xff0c;或者保存目录结构信息&#xff0c;来对项目进行说明 一、查看文件结构 1. tree /? 查看tree命令操作 2. tree&#xff08;只展示文件夹&#xff09; tree 3.tree /F&#xff08;文件夹文件都展示&#xff09; tree /F 二、保存文件…

Dockerfile创建 LNMP 服务+Wordpress 网站平台

文章目录 一.环境及准备工作1.项目环境2.服务器环境3.任务需求 二.Linux 系统基础镜像三.docker构建Nginx1.建立工作目录上传安装包2.编写 Dockerfile 脚本3.准备 nginx.conf 配置文件4.生成镜像5.创建自定义网络6.启动镜像容器7.验证 nginx 四.docker构建Mysql1. 建立工作目录…

JWT令牌验证

目录 一、JWT介绍 二、安装依赖 三、登陆接口 1、令牌工具类 2、接口代码 四、说明 一、JWT介绍 JWT全称&#xff1a;JSON Web Token &#xff08;官网&#xff1a;JSON Web Tokens - jwt.io&#xff09; 定义了一种简洁的、自包含的格式&#xff0c;用于在通信双方以json…

用户登录实现

参考博文&#xff1a; 01 技术太卷我学APEX-定制验证方案_白龙马5217的博客-CSDN博客https://blog.csdn.net/html5builder/article/details/128662070 创建函数 添加参数 函数 create or replace function "F_LOGIN" (p_username in VARCHAR2,p_password in varch…

【文化课学习笔记】【化学】非金属及其化合物

【化学】必修一&#xff1a;非金属及其化合物 硅及其化合物 硅单质 物理性质 单晶硅的结构与金刚石类似&#xff0c;为正四面体的立体网状结构。晶体中每个硅原子与其他四个硅原子相连接。\(1\mathrm{mol}\) 硅单质还有 \(\mathrm{2N_A}\) 个 \(\mathrm{Si-Si}\) 键&#xff1b…

安装pyrender和OSMesa

1&#xff09;安装 pyrender Pyrender是一个基于OpenGL的库&#xff0c;可以加载和渲染三维网格、点云、相机等对象3。 pip install pyrender 2&#xff09;理解PyOpenGL和OSMesa的关系是: PyOpenGL是Python的OpenGL绑定库&#xff08;接口壳子&#xff09;,它提供了在Python中…

openstack搭建

1 设置root密码&#xff1a;sudo passwd root 2 网络配置&#xff1a;虚拟机安装是选择nat映射&#xff0c;系统安装成功后直接配置vmnet8的地址段即可&#xff08;操作系统正常安装即可&#xff0c;虚拟机内存大于4G即可&#xff09;&#xff1b; 3 安装ssh 在命令行输入 “su…

【C语言】美元名字和面额对应问题

题目 美元硬币从小到大分为1美分&#xff08;penny&#xff09;5美分&#xff08;nickel&#xff09;10美分&#xff08;dime&#xff09;25美分&#xff08;quarter&#xff09;和50美分&#xff08;half-dollar&#xff09;&#xff0c;写一个程序实现当给出一个数字面额可以…

2023面试八股文 ——Java基础知识

Java基础知识 一.Java概述何为编程什么是Javajdk1.5之后的三大版本JVM、JRE和JDK的关系什么是跨平台性&#xff1f;原理是什么Java语言有哪些特点什么是字节码&#xff1f;采用字节码的大好处是什么什么是Java程序的主类&#xff1f;应用程序和小程序的主类有何不同&#xff1f…

Android学习之路(8) Activity

本节引言&#xff1a; 本节开始讲解Android的四大组件之一的Activity(活动)&#xff0c;先来看下官方对于Activity的介绍&#xff1a; 移动应用体验与桌面体验的不同之处在于&#xff0c;用户与应用的互动并不总是在同一位置开始&#xff0c;而是经常以不确定的方式开始。例如&…

【自动化测试】测试开发工具大合集

收集和整理各种测试工具&#xff0c;自动化测试工具&#xff0c;自动化测试框架&#xff0c;觉得有帮助记得三连一下。 欢迎提交各类测试工具到本博客。 通用测试框架 JUnit: 最著名的xUnit类的单元测试框架&#xff0c;但是不仅仅可以做单元测试。TestNG: 更强大的Java测试框…

pytorch 线性层Linear详解

线性层就是全连接层&#xff0c;以一个输入特征数为2&#xff0c;输出特征数为3的线性层为例&#xff0c;其网络结构如下图所示&#xff1a; 输入输出数据的关系如下&#xff1a; 写成矩阵的形式就是&#xff1a; 下面通过代码进行验证&#xff1a; import torch.nn as nn …