事务到底是隔离的还是不隔离的 (具体)

news2024/9/21 0:45:25

遇到不明白的慢慢往后读,一下你就明白了。

下面是一个只有两行的表的初始化语句

mysql> CREATE TABLE `t` (
`id` int(11) NOT NULL,
`k` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, k) values(1,1),(2,2);

 

         这里,我们需要注意的是事务的启动时机。

        begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作
InnoDB 表的语句,事务才真正启动。如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令

        第一种启动方式,一致性视图是在第执行第一个快照读语句时创建的;
        第二种启动方式,一致性视图是在执行 start transaction with consistent
snapshot 时创建的

    我们这里都是默认autocommit=1。

        在这个例子中,事务 C 没有显式地使用 begin/commit,表示这个 update 语句本身就是
一个事务,语句完成的时候会自动提交。事务 B 在更新了行之后查询 ; 事务 A 在一个只读
事务中查询,并且时间顺序上是在事务 B 的查询之后。

        这时,如果我告诉你事务 B 查到的 k 的值是 3,而事务 A 查到的 k 的值是 1,你是不是感
觉有点晕呢?      

        在 MySQL 里,有两个“视图”的概念:

        一个是 view。它是一个用查询语句定义的虚拟表,在调用的时候执行查询语句并生成结
果。创建视图的语法是 create view … ,而它的查询方法与表一样。
        另一个是 InnoDB 在实现 MVCC 时用到的一致性读视图,即 consistent read view,用
于支持 RC(Read Committed,读提交)和 RR(Repeatable Read,可重复读)隔离
级别的实现

“快照”在 MVCC 里是怎么工作的?

        在可重复读隔离级别下,事务在启动的时候就“拍了个快照”。注意,这个快照是基于整库
的。
        这时,你会说这看上去不太现实啊。如果一个库有 100G,那么我启动一个事务,MySQL
就要拷贝 100G 的数据出来,这个过程得多慢啊。可是,我平时的事务执行起来很快啊

        实际上,我们并不需要拷贝出这 100G 的数据。我们先来看看这个快照是怎么实现的。

        InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时
候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的.

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

        也就是说,数据表中的一行记录,其实可能有多个版本 (row),每个版本有自己的 row
trx_id.

        如图 2 所示,就是一个记录被多个事务连续更新后的状态。

图 2 行状态变更图 

        图中虚线框里是同一行数据的 4 个版本,当前最新版本是 V4,k 的值是 22,它是被
transaction id 为 25 的事务更新的,因此它的 row trx_id 也是 25 

        语句更新会生成 undo log(回滚日志)吗?那么,undo log 在哪呢?

        实际上,图 2 中的三个虚线箭头,就是 undo log;而 V1、V2、V3 并不是物理上真实存
在的,而是每次需要的时候根据当前版本和 undo log 计算出来的。比如,需要 V2 的时
候,就是通过 V4 依次执行 U3、U2 算出来.

        明白了多版本和 row trx_id 的概念后,我们再来想一下,InnoDB 是怎么定义那
个“100G”的快照的。

        按照可重复读的定义,一个事务启动的时候,能够看到所有已经提交的事务结果。但是之
后,这个事务执行期间,其他事务的更新对它不可见。

        因此,一个事务只需要在启动的时候声明说,“以我启动的时刻为准,如果一个数据版本是
在我启动之前生成的,就认;如果是我启动以后才生成的,我就不认,我必须要找到它的上
一个版本”。

        当然,如果“上一个版本”也不可见,那就得继续往前找。还有,如果是这个事务自己更新
的数据,它自己还是要认的.

        在实现上, InnoDB 为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正
在“活跃”的所有事务 ID。“活跃”指的就是,启动了但还没提交。

        数组里面事务 ID 的最小值记为低水位,当前系统里面已经创建过的事务 ID 的最大值加 1
记为高水位。

        这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)。

        而数据版本的可见性规则,就是基于数据的 row trx_id 和这个一致性视图的对比结果得到
的。

        这个视图数组把所有的 row trx_id 分成了几种不同的情况。

     图 3 数据版本可见性规则 

这样,对于当前事务的启动瞬间来说,一个数据版本的 row trx_id,有以下几种可能:

        1. 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数
据是可见的;

        2. 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;

        3. 如果落在黄色部分,那就包括两种情况

                 若 row trx_id 在数组中,表示这个版本是由还没提交的事务生成的,不可见;

                 若 row trx_id 不在数组中,表示这个版本是已经提交了的事务生成的,可见 .

        比如,对于图 2 中的数据来说,如果有一个事务,它的低水位是 18,那么当它访问这一行
数据时,就会从 V4 通过 U3 计算出 V3,所以在它看来,这一行的值是 11.

        你看,有了这个声明后,系统里面随后发生的更新,是不是就跟这个事务看到的内容无关了
呢?因为之后的更新,生成的版本一定属于上面的 2 或者 3(a) 的情况,而对它来说,这些
新的数据版本是不存在的,所以这个事务的快照,就是“静态”的了。

        所以你现在知道了,InnoDB 利用了“所有数据都有多个版本”的这个特性,实现了“秒
级创建快照”的能力。

查询逻辑

        接下来,我们继续看一下图 1 中的三个事务,分析下事务 A 的语句返回的结果,为什么是
k=1。

这里,我们不妨做如下假设:

1. 事务 A 开始前,系统里面只有一个活跃事务 ID 是 99;
2. 事务 A、B、C 的版本号分别是 100、101、102,且当前系统里只有这四个事务;
3. 三个事务开始前,(1,1)这一行数据的 row trx_id 是 90。

这样,事务 A 的视图数组就是 [99,100], 事务 B 的视图数组是 [99,100,101], 事务 C 的视
图数组是 [99,100,101,102]。

为了简化分析,我先把其他干扰语句去掉,只画出跟事务 A 查询逻辑有关的操作:

图 4 事务 A 查询数据逻辑图

从图中可以看到,第一个有效更新是事务 C,把数据从 (1,1) 改成了 (1,2)。这时候,这个
数据的最新版本的 row trx_id 是 102,而 90 这个版本已经成为了历史版本。

第二个有效更新是事务 B,把数据从 (1,2) 改成了 (1,3)。这时候,这个数据的最新版本(即
row trx_id)是 101,而 102 又成为了历史版本。

你可能注意到了,在事务 A 查询的时候,其实事务 B 还没有提交,但是它生成的 (1,3) 这
个版本已经变成当前版本了。但这个版本对事务 A 必须是不可见的,否则就变成脏读了。

好,现在事务 A 要来读数据了,它的视图数组是 [99,100]。当然了,读数据都是从当前版
本读起的。所以,事务 A 查询语句的读数据流程是这样的:

找到 (1,3) 的时候,判断出 row trx_id=101,比高水位大,处于红色区域,不可见;
接着,找到上一个历史版本,一看 row trx_id=102,比高水位大,处于红色区域,不可
见;
再往前找,终于找到了(1,1),它的 row trx_id=90,比低水位小,处于绿色区域,可
见 

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

这个判断规则是从代码逻辑直接转译过来的,但是正如你所见,用于人肉分析可见性很麻
烦。

所以,我来给你翻译一下。一个数据版本,对于一个事务视图来说,除了自己的更新总是可
见以外,有三种情况:

1. 版本未提交,不可见;
2. 版本已提交,但是是在视图创建后提交的,不可见;
3. 版本已提交,而且是在视图创建前提交的,可见。

现在,我们用这个规则来判断图 4 中的查询结果,事务 A 的查询语句的视图数组是在事务
A 启动的时候生成的,这时候:

(1,3) 还没提交,属于情况 1,不可见;
(1,2) 虽然提交了,但是是在视图数组创建之后提交的,属于情况 2,不可见;
(1,1) 是在视图数组创建之前提交的,可见。

你看,去掉数字对比后,只用时间先后顺序来判断,分析起来是不是轻松多了。所以,后面
我们就都用这个规则来分析

更新逻辑

事务 B 的 update 语句,如果按照一致性读,好像结果不对哦? 

你看图 5 中,事务 B 的视图数组是先生成的,之后事务 C 才提交,不是应该看不见 (1,2)
吗,怎么能算出 (1,3) 来?

图 5 事务 B 更新逻辑图 

 是的,如果事务 B 在更新之前查询一次数据,这个查询返回的 k 的值确实是 1。

但是,当它要去更新数据的时候,就不能再在历史版本上更新了,否则事务 C 的更新就丢
失了。因此,事务 B 此时的 set k=k+1 是在(1,2)的基础上进行的操作。

所以,这里就用到了这样一条规则:更新数据都是先读后写的,而这个读,只能读当前的
值,称为“当前读”(current read)

因此,在更新的时候,当前读拿到的数据是 (1,2),更新后生成了新版本的数据 (1,3),这个
新版本的 row trx_id 是 101

所以,在执行事务 B 查询语句的时候,一看自己的版本号是 101,最新数据的版本号也是
101,是自己的更新,可以直接使用,所以查询得到的 k 的值是 3

这里我们提到了一个概念,叫作当前读。其实,除了 update 语句外,select 语句如果加
锁,也是当前读

所以,如果把事务 A 的查询语句 select * from t where id=1 修改一下,加上 lock in
share mode 或 for update,也都可以读到版本号是 101 的数据,返回的 k 的值是 3。下
面这两个 select 语句,就是分别加了读锁(S 锁,共享锁)和写锁(X 锁,排他锁)。

再往前一步,假设事务 C 不是马上提交的,而是变成了下面的事务 C’,会怎么样呢?

 

图 6 事务 A、B、C'的执行流程

 

事务 C’的不同是,更新后并没有马上提交,在它提交前,事务 B 的更新语句先发起了。
前面说过了,虽然事务 C’还没提交,但是 (1,2) 这个版本也已经生成了,并且是当前的最
新版本。那么,事务 B 的更新语句会怎么处理呢? 

这时候,我们在上一篇文章中提到的“两阶段锁协议”就要上场了。事务 C’没提交,也就
是说 (1,2) 这个版本上的写锁还没释放。而事务 B 是当前读,必须要读最新版本,而且必须
加锁,因此就被锁住了,必须等到事务 C’释放这个锁,才能继续它的当前读。

图 7 事务 B 更新逻辑图(配合事务 C') 

到这里,我们把一致性读、当前读和行锁就串起来了。

现在,我们再回到文章开头的问题:事务的可重复读的能力是怎么实现的?

可重复读的核心就是一致性读(consistent read);而事务更新数据的时候,只能用当前
读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。

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

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

相关文章

ADS版图画封装联合仿真学习视频

ADS版图画封装 因为晶体管ATF54143在ADS中是没有封装的,所以要在ADS中画ATF54143的封装,操作步骤如下: 在ADS中新建layout,命名为ATF54143_layout, 根据datasheet知道封装的大小,进行绘制 在layout的con…

GO语言基础语法探究:简洁高效的编程之道

文章目录 前言Go词法单元token标识符关键字( 25个 )内置数据类型标识符( 20个 )内置函数( 15个 )常量值标识符( 4个)空白标识符( 1个 ) 操作符和分隔符字面常…

通向架构师的道路之基于数据库的权限系统的设计

一、权限系统 这一天将讲述一个基本的基于数据库的权限管理系统的设计,在这一天的课程的最后将讲述“左右值无限分类实现算法”如何来优化“系统菜单”的结构而告终。今天的内容和前几天的基础框架是一样的它们都属于基础知识,在这些基础知识上还可以扩…

Leetcode 剑指 Offer II 038. 每日温度

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer(专项突击版)系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 请根据每日 气温 列表 temperatures ,重新生成一个列…

第5集丨Vue 江湖 —— 监视属性/侦听属性

目录 一、基本使用1.1 watch配置监视1.2 vm.$watch动态监视1.3 深度监视(deep watch)1.4 简写形式 二、computed和watch的对比2.1 使用watch实现setTimeout操作2.2 用computed无法实现setTimeout 三、其他注意事项3.1 vue devtools的bug3.2 xxxyyy格式3.3 将window传入data中 V…

高并发负载均衡---LVS

目录 前言 一:负载均衡概述 二:为啥负载均衡服务器这么快呢? ​编辑 2.1 七层应用程序慢的原因 2.2 四层负载均衡器LVS快的原因 三:LVS负载均衡器的三种模式 3.1 NAT模式 3.1.1 什么是NAT模式 3.1.2 NAT模式实现LVS的缺点…

c++--二叉树应用

1.根据二叉树创建字符串 力扣 给你二叉树的根节点 root ,请你采用前序遍历的方式,将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串。 空节点使用一对空括号对 "()" 表示,转化后需要省略所有不影响字符…

【react】react中BrowserRouter和HashRouter的区别:

文章目录 1.底层原理不一样:2.path衣现形式不一样3.刷新后对路山state参数的影响4.备注: HashRouter可以用于解决一些路径错误相关的问题 1.底层原理不一样: BrowserRouter使用的是H5的history API,不兼容IE9及以下版不。 HashRouter使用的是URL的哈希值。 2.path衣…

Element-ui中分页器的使用

<template>中写&#xff1a; js中写&#xff1a;

pytorch的CrossEntropyLoss交叉熵损失函数默认reduction是平均值

pytorch中使用nn.CrossEntropyLoss()创建出来的交叉熵损失函数计算损失默认是求平均值的&#xff0c;即多个样本输入后获取的是一个均值标量&#xff0c;而不是样本大小的向量。 net nn.Linear(4, 2) loss nn.CrossEntropyLoss() X torch.rand(10, 4) y torch.ones(10, dt…

认识 MyBatis + MyBatis如何使用 (MyBatis操作数据库)

前言 本篇介绍了什么是MyBatis&#xff0c;MyBatis的前期配置&#xff0c;MyBatis操作数据库的两种方式&#xff1a;.xml文件 / 注释&#xff0c;使用MyBatis的一些注意&#xff0c;如有错误&#xff0c;请在评论区指正&#xff0c;让我们一起交流&#xff0c;共同进步&#x…

AI 一键去水印:教你无限量使用商业图片的技巧

场景再现 刚开始注册账号(啥账号具体不表了&#xff0c;小编不喜欢的那个)&#xff0c;想弄个闪亮&#xff0c;好看&#xff0c;有个性化的 Logo。作为一名非专美工小白人员&#xff0c;网上翻了很久作图工具&#xff0c;要么就是不好用&#xff0c;好用的大部分都收费。最后没…

如何从零开始配置前端环境以及安装必备的软件插件

文章目录 前言一、VSCode&#xff08;Visual Studio Code&#xff09;1.1 VSCode介绍与安装1.2 VSCode 常用插件安装1.3 VSCode代码格式化 二、Node.js2.1 Node的介绍与安装2. yarn的安装 三、Webpcak3.1 Webpcak的介绍与安装 四、Vue CLI4.1 Vue CLI的安装与介绍 五、初始化Vu…

设计模式-迭代器模式在Java中使用示例

场景 为开发一套销售管理系统&#xff0c;在对该系统进行分析和设计时&#xff0c;发现经常需要对系统中的商品数据、客户数据等进行遍历&#xff0c; 为了复用这些遍历代码&#xff0c;开发人员设计了一个抽象的数据集合类AbstractObjectList&#xff0c;而将存储商品和客户…

MaskedTextBox基本用法

作用&#xff1a;是一个文本输入框&#xff0c;可以指定文本的输入规则。 常用属性&#xff1a; 规定输入的格式 输入字符的占位符&#xff0c;获取输入的数据后留意处理 常用事件&#xff1a; 当输入字符不符合规则时发生 后台代码示范&#xff1a; //掩码文本控件输入不满…

快速实现一个div的水平垂直居中

效果 实现 给父盒子宽高和flex&#xff0c;子盒子margin&#xff1a;auto 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-sc…

管理类联考——写作——论说文——实战篇——行文篇——通用性强,解释多种现象的经典理论——谈必要

前言 本节内容涉及“社会分工理论”“资源稀缺性”“瓶颈理论”等理论。这些理论一般用在“利大于弊式结构”中“整体有必要”的部分&#xff0c;也可用于“AB二元类”题目“谈好处”的部分。 需要注意的是&#xff0c;“有好处”一般指有它更好&#xff1b;“有必要”一般指没…

基于人工智能的中医图像分类系统设计与实现

华佗AI 《支持中医,永远传承古老文化》 本存储库包含一个针对中药的人工智能图像分类系统。该项目的目标是通过输入图像准确识别和分类各种中草药和成分。 个人授权许可证 版权所有 2023至2050特此授予任何获得华佗AI应用程序(以下简称“软件”)副本的人免费许可,可根据以…

285 · 高楼大厦

链接&#xff1a;LintCode 炼码 - ChatGPT&#xff01;更高效的学习体验&#xff01; 题解&#xff1a; 1.从左往右维护一个单调递减&#xff0c;栈的长度就是&#xff0c;可以看到最多的高楼 2.从右往后也是维护一个单调递减的栈 class Solution { public:/*** param arr:…

Python中的PDF文本提取:使用fitz和wxPython库(带进度条)

引言&#xff1a; 处理大量PDF文档的文本提取任务可能是一项繁琐的工作。本文将介绍一个使用Python编写的工具&#xff0c;可通过简单的操作一键提取大量PDF文档中的文本内容&#xff0c;极大地提高工作效率。 import wx import pathlib import fitzclass PDFExtractor(wx.Fr…