「mysql是怎样运行的」第19章 从猫爷被杀说起---事务简介

news2024/9/27 7:20:12

「mysql是怎样运行的」第19章 从猫爷被杀说起—事务简介

文章目录

  • 「mysql是怎样运行的」第19章 从猫爷被杀说起---事务简介
    • @[toc]
    • 一、事务的起源
      • 概述
      • 原子性(Atomicity)
      • 隔离性(Isolation)
      • 一致性(Consistency)
      • 持久性(Durability)
    • 二、事务的概念

一、事务的起源

概述

对于大部分程序员来说,他们的任务就是把现实世界的业务场景映射到数据库世界。比如银行为了存储人们的账户信息会建立一个 account 表:

CREATE TABLE account (
id INT NOT NULL AUTO_INCREMENT COMMENT '自增id', name VARCHAR(100) COMMENT '客户名称',
balance INT COMMENT '余额',
PRIMARY KEY (id)
) Engine=InnoDB CHARSET=utf8;

狗哥和猫爷是一对好基友,他们都到银行开一个账户,他们在现实世界中拥有的资产就会体现在数据库世界的 account 表中。比如现在狗哥有 11 元,猫爷只有 2 元,那么现实中的这个情况映射到数据库的 account 表就是这样:

image-20230126215047586

在某个特定的时刻,狗哥猫爷这些家伙在银行所拥有的资产是一个特定的值,这些特定的值也可以被描述为账户在这个特定的时刻现实世界的一个状态。随着时间的流逝,狗哥和猫爷可能陆续进行向账户中存钱、取钱或者向别人转账等操作,这样他们账户中的余额就可能发生变动,每一个操作都相当于现实世界中账户的一次状态转换。数据库世界作为现实世界的一个映射,自然也要进行相应的变动。不变不知道,一变吓一跳,现实世界中一些看似很简单的状态转换,映射到数据库世界却不是那么容易的。比方说有一次猫爷在赌场赌博输了钱,急忙打电话给狗哥要借10块钱,不然那些看场子的就会把自己剁了。现实世界中的狗哥走向了ATM机,输入了猫爷的账 号以及10元的转账金额,然后按下确认,狗哥就拔卡走人了。对于数据库世界来说,相当于执行了下边这两条语 句:

UPDATE account SET balance = balance - 10 WHERE id = 1;
UPDATE account SET balance = balance + 10 WHERE id = 2;

但是这里头有个问题,上述两条语句只执行了一条时忽然服务器断电了咋办?把狗哥的钱扣了,但是没给猫爷转过去,那猫爷还是逃脱不了被砍死的噩运~ 即使对于单独的一条语句,我们前边唠叨 Buffer Pool 时也说过, 在对某个页面进行读写访问时,都会先把这个页面加载到 Buffer Pool 中,之后如果修改了某个页面,也不会立即把修改同步到磁盘,而只是把这个修改了的页面加到 Buffer Pool 的 flush链表 中,在之后的某个时间点才会刷新到磁盘。如果在将修改过的页刷新到磁盘之前系统崩溃了那岂不是猫爷还是要被砍死?或者在刷新磁盘的过 程中(只刷新部分数据到磁盘上)系统奔溃了猫爷也会被砍死?

怎么才能保证让可怜的猫爷不被砍死呢?其实再仔细想想,我们只是想让某些数据库操作符合现实世界中状态转换的规则而已,设计数据库的大叔们仔细盘算了盘算,现实世界中状态转换的规则有好几条,待我们慢慢道来。


原子性(Atomicity)

现实世界中转账操作是一个不可分割的操作,也就是说要么压根儿就没转,要么转账成功,不能存在中间的状态,也就是转了一半的这种情况。设计数据库的大叔们把这种要么全做,要么全不做的规则称之为原子性。但是在现实世界中的一个不可分割的操作却可能对应着数据库世界若干条不同的操作,数据库中的一条操作也可能被分解成若干个步骤(比如先修改缓存页,之后再刷新到磁盘等),最要命的是在任何一个可能的时间都可能发生意想不到的错误(可能是数据库本身的错误,或者是操作系统错误,甚至是直接断电之类的)而使操作执行不下去,所以猫爷可能会被砍死。为了保证在数据库世界中某些操作的原子性,设计数据库的大叔需要费一些心机来保证如果在执行操作的过程中发生了错误,把已经做了的操作恢复成没执行之前的样子,这也是我们后边章节要仔细唠叨的内容。


隔离性(Isolation)

现实世界中的两次状态转换应该是互不影响的,比如说狗哥向猫爷同时进行的两次金额为5元的转账(假设可以 在两个ATM机上同时操作)。那么最后狗哥的账户里肯定会少10元,猫爷的账户里肯定多了10元。但是到对应的数据库世界中,事情又变的复杂了一些。为了简化问题,我们粗略的假设狗哥向猫爷转账5元的过程是由下边几 个步骤组成的:

  • 步骤一:读取狗哥账户的余额到变量A中,这一步骤简写为 read(A) 。
  • 步骤二:将狗哥账户的余额减去转账金额,这一步骤简写为 A = A - 5 。
  • 步骤三:将狗哥账户修改过的余额写到磁盘里,这一步骤简写为 write(A) 。
  • 步骤四:读取猫爷账户的余额到变量B,这一步骤简写为 read(B) 。
  • 步骤五:将猫爷账户的余额加上转账金额,这一步骤简写为 B = B + 5 。
  • 步骤六:将猫爷账户修改过的余额写到磁盘里,这一步骤简写为 write(B) 。

我们将狗哥向猫爷同时进行的两次转账操作分别称为 T1 和 T2 ,在现实世界中 T1 和 T2 是应该没有关系的,可 以先执行完 T1 ,再执行 T2 ,或者先执行完 T2 ,再执行 T1 ,对应的数据库操作就像这样:

image-20230126231157462

但是很不幸,真实的数据库中 T1 和 T2 的操作可能交替执行,比如这样:

image-20230126231622136

如果按照上图中的执行顺序来进行两次转账的话,最终狗哥的账户里还剩 6元钱,相当于只扣了5元钱,但是猫爷的账户里却成了12 元钱,相当于多了10元钱,这银行岂不是要亏死了?

所以对于现实世界中状态转换对应的某些数据库操作来说,不仅要保证这些操作以 原子性 的方式执行完成,而且要保证其它的状态转换不会影响到本次状态转换,这个规则被称之为 隔离性 。这时设计数据库的大叔们就需要采取一些措施来让访问相同数据(上例中的A账户和B账户)的不同状态转换(上例中的 T1 和 T2 )对应的数据库操作的执行顺序有一定规律,这也是我们后边章节要仔细唠叨的内容。


一致性(Consistency)

我们生活的这个世界存在着形形色色的约束,比如身份证号不能重复,性别只能是男或者女,高考的分数只能在 0~750之间,人民币面值最大只能是100(现在是2019年),红绿灯只有3种颜色,房价不能为负的,学生要听 老师话,吧啦吧啦有点儿扯远了~ 只有符合这些约束的数据才是有效的,比如有个小孩儿跟你说他高考考了 1000分,你一听就知道他胡扯呢。数据库世界只是现实世界的一个映射,现实世界中存在的约束当然也要在数据 库世界中有所体现。如果数据库中的数据全部符合现实世界中的约束(all defined rules),我们说这些数据就是一致的,或者说符合一致性的。

如何保证数据库中数据的一致性(就是符合所有现实世界的约束)呢?这其实靠两方面的努力:

  • 数据库本身能为我们保证一部分一致性需求(就是数据库自身可以保证一部分现实世界的约束永远有效)。

    我们知道 MySQL 数据库可以为表建立主键、唯一索引、外键、声明某个列为 NOT NULL 来拒绝 NULL 值的插 入。比如说当我们对某个列建立唯一索引时,如果插入某条记录时该列的值重复了,那么 MySQL 就会报错并 且拒绝插入。除了这些我们已经非常熟悉的保证一致性的功能, MySQL 还支持 CHECK 语法来自定义约束,比如这样:

    CREATE TABLE account (
    id INT NOT NULL AUTO_INCREMENT COMMENT '自增id', name VARCHAR(100) COMMENT '客户名称',
    balance INT COMMENT '余额',
    PRIMARY KEY (id),
    CHECK (balance >= 0)
    );
    

    上述例子中的 CHECK 语句本意是想规定 balance 列不能存储小于0的数字,对应的现实世界的意思就是银行账户余额不能小于0。但是很遗憾,MySQL仅仅支持CHECK语法,但实际上并没有一点卵用,也就是说即使我们使用上述带有CHECK 子句的建表语句来创建 account 表,那么在后续插入或更新记录时, MySQL 并不会去检查 CHECK 子句中的约束是否成立。

    小贴士:

    其它的一些数据库,比如SQL Server或者Oracle支持的CHECK语法是有实实在在的作用的,每次进行插入或更新记录之前都会检查一下数据是否符合CHECK子句中指定的约束条件是否成立,如果不成立的话就会拒绝插入或更新。

    虽然 CHECK 子句对一致性检查没什么卵用,但是我们还是可以通过定义触发器的方式来自定义一些约束条件 以保证数据库中数据的一致性。

    小贴士:

    触发器是MySQL基础内容中的知识,本书是一本MySQL进阶的书籍,如果你不了解触发器,那恐怕 要找本基础内容的书籍来看看了。

  • 更多的一致性需求需要靠写业务代码的程序员自己保证。

    为建立现实世界和数据库世界的对应关系,理论上应该把现实世界中的所有约束都反应到数据库世界中,但是很不幸,在更改数据库数据时进行一致性检查是一个耗费性能的工作,比方说我们为 account 表建立了一 个触发器,每当插入或者更新记录时都会校验一下 balance 列的值是不是大于0,这就会影响到插入或更新的速度。仅仅是校验一行记录符不符合一致性需求倒也不是什么大问题,有的一致性需求简直变态,比方说 银行会建立一张代表账单的表,里边儿记录了每个账户的每笔交易,每一笔交易完成后,都需要保证整个系统的余额等于所有账户的收入减去所有账户的支出。如果在数据库层面实现这个一致性需求的话,每次发生交易时,都需要将所有的收入加起来减去所有的支出,再将所有的账户余额加起来,看看两个值相不相等。 这不是搞笑呢么,如果账单表里有几亿条记录,光是这个校验的过程可能就要跑好几个小时,也就是说你在 煎饼摊买个煎饼,使用银行卡付款之后要等好几个小时才能提示付款成功,这样的性能代价是完全承受不起的。

    现实生活中复杂的一致性需求比比皆是,而由于性能问题把一致性需求交给数据库去解决这是不现实的,所以这个锅就甩给了业务端程序员。比方说我们的 account 表,我们也可以不建立触发器,只要编写业务的程 序员在自己的业务代码里判断一下,当某个操作会将 balance 列的值更新为小于0的值时,就不执行该操作 就好了嘛!

我们前边唠叨的 原子性隔离性 都会对 一致性 产生影响,比如我们现实世界中转账操作完成后,有一个一致性需求就是参与转账的账户的总的余额是不变的。如果数据库不遵循 原子性要求,也就是转了一半就不转 了,也就是说给狗哥扣了钱而没给猫爷转过去,那最后就是不符合一致性需求的;类似的,如果数据库不遵循隔离性 要求,就像我们前边唠叨隔离性时举的例子中所说的,最终狗哥账户中扣的钱和猫爷账户中涨的钱可能就 不一样了,也就是说不符合一致性需求了。所以说,数据库某些操作的原子性和隔离性都是保证一致性的一种 手段,在操作执行完成后保证符合所有既定的约束则是一种结果。那满足原子性和 隔离性的操作一定就满足一致性 么?那倒也不一定,比如说狗哥要转账20元给猫爷,虽然在满足 原子性 和 隔离性 ,但转账完成了之后 狗哥的账户的余额就成负的了,这显然是不满足 一致性 的。那不满足 原子性隔离性 的操作就一定不满足一致性 么?这也不一定,只要最后的结果符合所有现实世界中的约束,那么就是符合 一致性 的。


持久性(Durability)

当现实世界的一个状态转换完成后,这个转换的结果将永久的保留,这个规则被设计数据库的大叔们称为 持久性 。比方说狗哥向猫爷转账,当ATM机提示转账成功了,就意味着这次账户的状态转换完成了,狗哥就可以拔卡 走人了。如果当狗哥走掉之后,银行又把这次转账操作给撤销掉,恢复到没转账之前的样子,那猫爷不就惨了, 又得被砍死了,所以这个 持久性是非常重要的。

当把现实世界的状态转换映射到数据库世界时, 持久性 意味着该转换对应的数据库操作所修改的数据都应该在 磁盘上保留下来,不论之后发生了什么事故,本次转换造成的影响都不应该被丢失掉(要不然猫爷还是会被砍 死)。


二、事务的概念

为了方便大家记住我们上边唠叨的现实世界状态转换过程中需要遵守的4个特性,**我们把原子性 ( Atomicity )、 隔离性 ( Isolation )、 一致性 ( Consistency )和 持久性 ( Durability )**这四个词 对应的英文单词首字母提取出来就是 A 、 I 、 C 、 D ,稍微变换一下顺序可以组成一个完整的英文单词:ACID 。想必大家都是学过初高中英语的, ACID 是英文酸的意思,以后我们提到 ACID 这个词儿,大家就应该想到原子性、一致性、隔离性、持久性这几个规则。另外,设计数据库的大叔为了方便起见,把需要保证 原子性 、 隔离性 、 一致性 和 持久性 的一个或多个数据库操作称之为一个事务 (英文名是: transaction )

我们现在知道 事务 是一个抽象的概念,它其实对应着一个或多个数据库操作,设计数据库的大叔根据这些操作所执行的不同阶段把事务大致上划分成了这么几个状态:

  • 活动的(active)

    事务对应的数据库操作正在执行过程中时,我们就说该事务处在 活动的 状态。

  • 部分提交的(partially committed)

    当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时,我们 就说该事务处在部分提交的状态

  • 失败的(failed)

    当事务处在活动的或者部分提交的状态时,可能遇到了某些错误(数据库自身的错误、操作系统错误或者 直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在失败的状态。

  • 中止的(aborted)

如果事务执行了半截而变为失败的状态,比如我们前边唠叨的狗哥向猫爷转账的事务,当狗哥账户的钱被扣除,但是猫爷账户的钱没有增加时遇到了错误,从而当前事务处在了失败的状态,那么就需要把已经修改的狗哥账户余额调整为未转账之前的金额,换句话说,就是要撤销失败事务对当前数据库造成的影响。书面一点的话,我们把这个撤销的过程称之为 回滚 。当 回滚 操作执行完毕时,也就是数据库恢复到了执行事 务之前的状态,我们就说该事务处在了 中止的 状态。

  • 提交的(committed)

    当一个处在 部分提交的 状态的事务将修改过的数据都同步到磁盘上之后,我们就可以说该事务处在了 提交的 状态。

随着事务对应的数据库操作执行到不同阶段,事务的状态也在不断变化,一个基本的状态转换图如下所示:

image-20230128120903946

从图中大家也可以看出了,只有当事务处于提交的或者中止的状态时,一个事务的生命周期才算是结束了。对于已经提交的事务来说,该事务对数据库所做的修改将永久生效,对于处于中止状态的事务,该事务对数据库所做的所有修改都会被回滚到没执行该事务之前的状态。

小贴士: 此贴士处纯属扯犊子,与正文没啥关系,纯属吐槽。大家知道我们的计算机术语基本上全是从英文翻译 成中文的,事务的英文是transaction,英文直译就是交易,买卖的意思,交易就是买的人付钱,卖的 人交货,不能付了钱不交货,交了货不付钱把,所以交易本身就是一种不可分割的操作。不知道是哪位 大神把transaction翻译成了事务(我想估计是他们也想不出什么更好的词儿,只能随便找一个了), 事务这个词儿完全没有交易、买卖的意思,所以大家理解起来也会比较困难,外国人理解transaction 可能更好理解一点吧~

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

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

相关文章

android UI优化的基本原理和实战方法

任何Android应用都需要UI跟用户交互.UI是否好坏更是直接影响到用户的体验.如今UI的优化视乎是应用开发中一个绕不过去的话题。所以本篇文章小编带大家全面了解Android ui优化的主要知识和优化方法。 一、UI优化 UI优化知识点主要分为三部分: 第一部分&#xff0c…

Linux简单介绍(基本涵盖日常使用到的各种shell知识点)

文章目录shell基础认知1. shell语言2. 脚本执行方式3. 快捷键4. 通配符5. 命令后跟的选项6. 逻辑运算 && ||7. 算术运算(equal,great,less)8. 目录或文件意义9. 规则10. vimshell脚本常规内容解释1. set -ex2. set -o pip…

Google Brain新提出的优化器“Lion”,效果要比Adam(W)更好

Google Brain新提出的优化器“Lion”,效果要比Adam(W)更好 论文地址:https://arxiv.org/abs/2302.06675代码地址:https://github.com/google/automl/blob/master/lion/lion_pytorch.py 1 简单、内存高效、运行速度更快 与 AdamW 和各种自适…

量子计算对网络安全的影响

量子计算的快速发展,例如 IBM 的 Quantum Condor 处理器具有 1000 个量子比特的容量,促使专家们宣称第四次工业革命即将实现“量子飞跃”。 量子计算机的指数处理能力已经受到政府和企业的欢迎。 由于从学术和物理原理到商业可用解决方案的不断转变&am…

Spark Explain:查看执行计划

Spark SQL explain 方法有 simple、extended、codegen、cost、formatted 参数,具体如下 目录一、基本语法二、执行计划处理流程三、具体案例一、基本语法 从 3.0 开始,explain 方法有一个新的 mode 参数,指定执行计划展示格式 只展示物理执…

都2023年了,竟然还有人问网络安全怎么入门?

工作一直忙碌,偶然翻了一下知乎,都2022年了,相关网课这么多了,还有人不知道怎么学习网络安全,不了解也就算了,竟然还有一批神仙也真敢回答,对这个行业了解各一知半解就当做这些萌新的启蒙老师了…

UDP与TCP协议

目录 UDP协议 协议报头 UDP协议特点: 应用场景: TCP TCP协议报头 确认应答机制 理解可靠性 超时重传机制 连接管理机制 三次握手: 四次挥手: 滑动窗口 如何理解缓冲区和滑动窗口? 倘若出现丢包&#xf…

05 DC-AC逆变器(DCAC Converter / Inverter)简介

文章目录0、概述逆变原理方波变换阶梯波变换斩控调制方式逆变器分类逆变器波形指标1、方波变换器A 单相单相全桥对称单脉冲调制移相单脉冲调制单相半桥2、方波变换器B 三相180度导通120度导通(线、相的关系与180度相反)3、阶梯波逆变器独立直流源二极管钳…

Esxi NAT网络搭建

前言 本文主要讲述如何在Esxi上只有一个公网IP情况下,实现内部虚拟机上网,以及外部对内部服务的访问,以及外网通过vpn访问内网; 环境 Esxi 6.7iKuai8 3.6.13OpenVPN 2.6一、创建虚拟路由 1.1 目的 虚拟路由,也就是常说的软路由;只有一个外网IP情况下,其他虚拟机需要上…

LeetCode刷题系列 -- 429. N 叉树的层序遍历

给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。示例 1:输入:root [1,null,3,2…

【每日阅读】JS知识(三)

var声明提升 js是一个解释性语言类型,预解析就是在执行代码之前对代码进行通读 var关键字是,在内存中声明一个变量名 js在代码执行之前 会经历两个环节 解释代码 和执行代码 声明式函数 内存中 先声明一个变量名是函数 这个名代表的是函数 乘法表 // for…

IP、ICMP、TCP和UDP校验和计算

一. 前言 计算网络数据包的校验和是机器自动完成,不需要手动计算。但是正因为如此,我们往往不会去深究校验和到底是怎么计算的,留下这一块盲区。虽然书上有大致介绍计算的方法,但是,“纸上得来终觉浅,绝知此…

二叉树——验证二叉搜索树

验证二叉搜索树 链接 给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下: 节点的左子树只包含 小于 当前节点的数。 节点的右子树只包含 大于 当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。 …

【Proteus仿真】【51单片机】粮仓温湿度控制系统设计

文章目录一、功能简介二、软件设计三、实验现象联系作者一、功能简介 本项目使用Proteus8仿真51单片机控制器,使用声光报警模块、LCD1602显示模块、DHT11温湿度模块、继电器模块、加热加湿除湿风扇等。 主要功能: 系统运行后,LCD1602显示传…

LeetCode 144. 二叉树的前序遍历

144. 二叉树的前序遍历 难度:easy\color{Green}{easy}easy 题目描述 给你二叉树的根节点 rootrootroot ,返回它节点值的 前序 遍历。 示例 1: 输入:root [1,null,2,3] 输出:[1,2,3]示例 2: 输入&#…

Web前端学习:三 - 练习

三六&#xff1a;风筝效果 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><style type"text/css">*{margin: 0;padding: 0;}.d1{width: 200px;height: 200px;background: yellow;position…

链表的排序:插入排序和归并排序

文章目录链表的排序&#xff1a;插入排序和归并排序147. 对链表进行插入排序148. 排序链表链表的排序&#xff1a;插入排序和归并排序 两道例题进行记录。 147. 对链表进行插入排序 题目链接&#xff1a;https://leetcode.cn/problems/insertion-sort-list/ 题目大意&#x…

计算机网络笔记 | 第一章:计算机网络概述(1.1-1.4小节知识点整理)

从专栏将讲述有关于计算机网络相关知识点&#xff0c;如果有想学习Java的小伙伴可以点击下方连接查看专栏&#xff0c;还有JavaEE部分 本专栏地址&#xff08;持续更新中&#xff09;&#xff1a;&#x1f525;计算机网络 MyBatis&#xff1a;✍️MyBatis Java入门篇&#xff1…

nginx安装部署实战手册

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录一、虚拟机安装nginx1.下载安装包2.安装编译工具和库文件3.编译安装4.启动nginx5.访问首页6.开机自启结尾一、虚拟机安装nginx 1.下载安装包 官网下载地址&#xf…

zabbix4.0-自定义脚本告警

目录 1、在zabbix-server端下载mailx 2、配置mailx配置文件 3、查看zabbix-server设置的 AlertScriptsPath变量 4、在对应路径下面编写邮件脚本 5、创建一个媒介类型 6、为用户指定媒介类型 7、更改触发器表达式进行测试 1、在zabbix-server端下载mailx [rootzabbix-serve…