个人主页:秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343
系列专栏:https://blog.csdn.net/qinjh_/category_12998168.html

9efbcbc3d25747719da38c01b3fa9b4f.gif

目录

CURD不加控制,会有什么障碍?

CURD满足什么属性,能处理上述问题?

什么是事务?

为什么会出现事务

事务的版本支持

事务提交方式

事务常见操作方式

事务隔离级别

如何理解隔离性1

隔离级别

查看与设置隔离性

读未提交【Read Uncommitted】

读提交【Read Committed】

可重复读【Repeatable Read】

串行化【serializable】

一致性(Consistency)

如何理解隔离性2

数据库并发的场景有三种:

读-写

3个记录隐藏列字段

undo 日志

模拟 MVCC

Read View

整体流程

RR 与 RC的本质区别

当前读和快照读在RR级别下的区别

RR 与 RC的本质区别


前言

hello! 各位铁子们大家好哇。

今日更新了MySQL事务管理的内容
欢迎大家关注点赞收藏⭐️留言

CURD不加控制,会有什么障碍?

CURD满足什么属性,能解决上述问题?

  1. 买票的过程得是原子的吧
  2. 买票互相应该不能影响吧
  3. 买完票应该要永久有效吧
  4. 买前,和买后都要是确定的状态吧

什么是事务?

事务就是一组DML语句组成,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部 失败,是一个整体。MySQL给予一种机制,保证我们达到这样的效果。事务还规定不同的客户端看到的 资料是不相同的。

事务就是要做的或所做的事情,核心用于处理管理量大,复杂度高的信息。假设一种场景:你毕业了, 学校的教务架构后台 MySQL 中,不在需要你的数据,要删除你的所有信息(一般不会:) ), 那么要删除你的 基本信息(姓名,电话,籍贯等)的同时,也删除和你有关的其他信息,比如:你的各科成绩,你在校表 现,甚至你在论坛发过的文章等。这样,就需要多条 MySQL 语句构成,那么所有这些操作合起来,就构 成了一个事务。

正如大家上面所说,一个 MySQL 数据库,可不止你一个事务在运行,同一时刻,甚至有大量的请求被包 装成事务,在向 MySQL 服务器发起事务处理请求。而每条事务至少一条 SQL ,最多很多 SQL ,这样如果大 家都访问同样的表数据,在不加保护的情况,就绝对会出现问题。甚至,基于事务由多条 SQL 构成,那 么,也会存在执行到一半出错或者不想再执行的情况,那么已经执行的怎么办呢?

所以,一个完整的事务,绝对不是轻松的 sql 集合,还需要满足如下四个属性:

  • 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不搞定,不会结束在中 间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个 事务从来没有执行过一样。
  • 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完 全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地达成预定的工 作。
  • 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务 并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交( Read uncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化 ( Serializable )
  • 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

上面四个属性,可能简称为 ACID 。

原子性(Atomicity,或称不可分割性)

一致性(Consistency)

隔离性(Isolation,又称独立性)

持久性(Durability)。

为什么会出现事务

伴随着数据库系统天生就有的.就是事务被 MySQL 编写者设计出来,本质是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型, 不需要我们去考虑各种各样的潜在错误和并发问题.可以想一下当我们使用事务时,要么提交,要么回滚,我 们不会去考虑网络异常了,服务器宕机了,同时更改一个数据怎么办对吧?因此事务本质上是为了应用层服 务的.而不

我们后面把 MySQL 中的一行信息,称为一行记录

事务的版本支持

在 MySQL 中只有应用了 Innodb 数据库引擎的数据库或表才协助事务, MyISAM 不承受。

查看数据库引擎

事务提交方式

事务的提交方式常见的有两种:

  • 自动提交
  • 手动提交

查看事务提交方式

用 SET 来改变 MySQL 的自动提交模式:

SET AUTOCOMMIT=0 禁止自动提交

SET AUTOCOMMIT=1 开启自动提交

事务常见操作方式

简单员工工资表

这里将mysql的默认隔离级别设置成读未提交 。

设置后,要重启客户端再查看

  • 创建测试表

  • 正常演示 - 证明事务的开始与回滚

查看事务是否自动提交。我们故意设置成自动提交,看看该选项是否影响begin

开始一个事务,可以用 start transaction,也可以 begin,推荐begin

这里有两个终端一起进行事务。

最开始时,表是空的。创建一个保存点s1,随后插入一条记录,右边查询该表,可以看见表里有内容了。接着继续创建保存点和插入记录。

回滚到保存点s3,发现王五的记录没了,回滚到s1,表里的记录都没了。

事务完成后,可以commit结束,相当于提交事务。

如果没创建保存点,rollback后,会直接把数据全部清空。

  • 非正常演示1 - 证明未commit,客户端崩溃,MySQL自动会回滚(隔离级别设置为读未提交)

由于没有commit,终端a异常终止后,数据就会自动回滚。

  • 非正常演示2 - 证明commit了,客户端崩溃,MySQL数据不会在受影响,已经持久化

  • 非正常演示3 - 对比试验。证明begin操作会自动更改提交方式,不会受MySQL是否自动提交影响

  • 非正常演示4 - 证明单条 SQL 与事务的关系


结论:

  • 只要输入begin或者start transaction,事务便必须要通过commit提交,才会持久化,与是 否设置set autocommit无关。
  • 事务可以手动回滚,同时,当运行异常,MySQL会自动回滚
  • 对于 InnoDB 每一条 SQL 语言都默认封装成事务,自动提交。(select有特殊情况,因为 MySQL 有 MVCC )
  • 从上面的例子,我们能看到事务本身的原子性(回滚),持久性(commit)

事务操作注意事项

  • 如果没有设置保存点,也可以回滚,只能回滚到事务的开始。直接使用 rollback(前提是事务 还没有提交)
  • 如果一个事务被提交了(commit),则不可以回退(rollback)
  • 可以选择回退到哪个保存点
  • InnoDB 协助事务, MyISAM 不协助事务
  • 开始事务可以使 start transaction 或者 begin

事务隔离级别

如何理解隔离性1

  • MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务方式进行
  • 一个事务可能由多条SQL构成,也就意味着,任何一个事务,都有执行前,执行中,执行后的阶 段。而所谓的原子性,其实就是让用户层,要么看到执行前,要么看到执行后。执行中出现问题, 可以随时回滚。于是单个事务,对用户表现出来的特性,就是原子性。
  • 但,毕竟所有事务都要有个执行过程,那么在多个事务各自执行多个SQL的时候,就还是有可能会 出现互相影响的情况。比如:多个事务同时访问同一张表,甚至同一行信息。
  • 就如同你妈妈给你说:你要么别学,要学就学到最好。至于你怎么学,中间有什么困难,你妈妈不 关心。那么你的学习,对你妈妈来讲,就是原子的。倘若你学习过程中,很容易受别人干扰,此 时,就需要将你的学习隔离开,保证你的学习环境是健康的。
  • 数据库中,为了保证事务执行过程中尽量不受干扰,就有了一个重要特征:隔离性
  • 数据库中,允许事务受不同程度的干扰,就有了一种重要特征:隔离级别

隔离级别

  • 读未提交【Read Uncommitted】通过: 在该隔离级别,所有的事务都能够看到其他事务没有提交的 执行结果。(实际生产中不可能利用这种隔离级别的),但是相当于没有任何隔离性,也会有很多 并发问题,如脏读,幻读,不可重复读等,我们上面为了做实验方便,用的就是这个隔离性。
  • 读提交【Read Committed】:该隔离级别是大多数数据库的默认的隔离级别(不是 MySQL 默 认的)。它满足了隔离的便捷定义:一个事务只能看到其他的已经提交的事务所做的改变。这种隔离 级别会引起不可重复读,即一个事务执行时,如果多次 select, 可能得到不同的结果。
  • 可重复读【Repeatable Read】: 这是 MySQL 默认的隔离级别,它确保同一个事务,在执行 中,多次读取操作数据时,会看到同样的数据行。但是会有幻读问题。
  • 串行化【Serializable】: 这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突, 从而克服了幻读的问题。它在每个读的材料行上面加上共享锁,。但是可能会导致超时和锁竞争 (这种隔离级别太极端,实际生产基本不使用)

隔离级别如何实现:隔离,基本都是经过锁实现的,不同的隔离级别,锁的使用是不同的。常见有,表锁,行锁,读锁,写锁,间隙锁(GAP),Next-Key锁(GAP+行锁)等。不过,我们目前现有该认识就行, 先关注上层使用。

查看与设置隔离性

查看

设置

设置当前会话 or 全局隔离级别语法

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}

设置当前会话隔离性,另起一个会话

全局隔离性不变,会话隔离性变为读提交

设置全局隔离性,另起一个会话

刚开始只有全局隔离性改变了,只有重新连接客户端,会话隔离性才会更新,如下图。

每次新登录mysql时,mysql会默认利用全局隔离对会话隔离进行设置。

读未提交【Read Uncommitted】

几乎没有加锁,尽管效率高,但是问题太多,严重不建议采用

一个事务在执行中,读到另一个执行中事务的更新(或其他执行)但未commit的数据,此种现象叫做脏读 (dirty read)

读提交【Read Committed】

可重复读【Repeatable Read】

如果将上面的终端A中的update操作,改成insert操作,会有什么问题??

串行化【serializable】

对所有执行全部加锁,进行串行化,不会有困难,然而只要串行化,效率很低,几乎完全不会被采用

总结:

  • 其中隔离级别越严格,安全性越高,但数据库的并发性能也就越低,往往需要在两者之间找一个平 衡点。
  • 不可重复读的重点是修改和删除:同样的条件, 你读取过的数据,再次读取出来发现值不一样了 幻读的重点在于新增:同样的条件, 第1次和第2次读出来的记录数不一样
  • 可重复读,一般情况下不要修改就是说明: mysql 默认的隔离级别
  • 上面的例子可以看出,事务也有长短事务这样的概念。事务间互相影响,指的是事务在并行执行的 时候,即都没有commit的时候,影响会比较大。

一致性(Consistency)

  • 事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只囊括事务 成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中 断,而该未完成的事务对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确(不一 致)的状态。因此一致性是通过原子性来保证的。
  • 其实一致性和用户的业务逻辑强相关,一般MySQL提供技术支持,但一致性还是要用户业务逻辑 做支撑,也就是,一致性,是由用户决定的。
  • 而技术上,通过AID保证C

如何理解隔离性2

数据库并发的场景有三种:

  • 读-读 :不存在任何问题,也不必须并发控制
  • 读-写 :有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
  • 写-写 :有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失

读-写

多版本并发控制( MVCC )是一种用来解决 读-写冲突 的无锁并发控制

为事务分配单向增长的事务ID,为每个修改保存一个版本,版本与事务ID关联,读操作只读该事务开始 前的数据库的快照。 所以 MVCC 可以为数据库应对以下问题

  • 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写管理也不用阻塞读操作,提高了数 据库并发读写的性能
  • 同时还可能解决脏读,幻读,不可重复读等事务隔离问题,但不能处理更新丢失问题

理解 MVCC 需要知道三个前提知识:

  • 3个记录隐藏字段
  • undo 日志
  • Read View

3个记录隐藏列字段

  • DB_TRX_ID :6 byte,最近修改( 修改/插入 )事务ID,记录创建这条记录/最后一次修改该记录的事 务ID
  • DB_ROLL_PTR : 7 byte,回滚指针,指向这条记录的上一个版本(简单理解成,指向历史版本就 行,这些数据一般在 undo log 中)
  • DB_ROW_ID : 6 byte,隐含的自增ID(隐藏主键),如果数据表没有主键, InnoDB 会自动以 DB_ROW_ID 产生一个聚簇索引
  • 补充:实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了

假设测试表结构是:

:就是上面描述的意思

我们目前并不知道创建该记录的事务ID,隐式主键,我们就默认设置成null,1。第一条记录也没有其他 版本,我们设置回滚指针为null。

undo 日志

这里不想细讲,但是有一件事情得说清楚, MySQL 将来是以服务进程的方式,在内存中运行。我们之前 所讲的所有机制:索引,事务,隔离性,日志等,都是在内存中完成的,即在 MySQL 内部的相关缓冲区 中,保存相关数据,完毕各种判断操作。然后在合适的时候,将相关数据刷新到磁盘当中的。

MySQL 中的一段内存缓冲区,用来保存日志数据的就 行。就是所以,我们这里理解undo log,简单理解成,就

模拟 MVCC

现在有一个事务10(仅仅为了好区分),对student表中记录进行修改(update):将name(张三)改成 name(李四)。

  • 事务10,源于要修改,所以要先给该记录加行锁。
  • 写 时拷贝)就是修改前,先将改行记录拷贝到undo log中,所以,undo log中就有了一行副本素材。(原理就
  • 所以现在 MySQL 中有两行同样的记录。现在修改原始记录中的name,改成 '李四'。并且修改原始 记录的隐藏字段 DB_TRX_ID 为当前 事务10 的ID, 大家默认从 10 开始,之后递增。而原始记录的回 滚指针 DB_ROLL_PTR 列,里面写入undo log中副本数据的地址,从而指向副本记录,既表示我的 上一个版本就是它。
  • 事务10提交,释放锁。

此时,最新的记录是’李四‘那条记录。

现在又有一个事务11,对student表中记录进行修改(update):将age(28)改成age(38)。

  • 事务11,因为也要修改,所以要先给该记录加行锁。
  • 修改前,现将改行记录拷贝到undo log中,所以,undo log中就又有了一行副本数据。此时,新的 副本,我们采用头插方式,插入undo log。
  • 现在修改原始记录中的age,改成 38。并且修改原始记录的隐藏字段 DB_TRX_ID 为当前 事务11 的 ID。而原始记录的回滚指针 DB_ROLL_PTR 列,里面写入undo log中副本数据的地址,从而指向副 本记录,既表示我的上一个版本就是它。
  • 事务11提交,释放锁。

这样,我们就有了一个基于链表记录的历史版本链。所谓的回滚,无非就是用历史数据,覆盖当前数 据。

上面的一个一个版本,大家能够称之为一个一个的快照。

上面是以更新(`upadte`)主讲的,若是是`delete`呢?一样的,别忘了,删数据不是清空,而是设置flag 为删除即可。也可能形成版本。

要被放入undo log中,倘若当前事务commit了,那么这个undo log 的历史insert记录就可以被清空了。就是假如是`insert`呢?因为`insert`是插入,也就是之前没有数据,那么`insert`也就没有历史版本。但是 一般为了回滚操作,insert的数据也

我们可能理解成,`update`和`delete`可以形成版本链,`insert`暂时不考虑。就是总结一下,也就

那么`select`呢?

:就是首先,`select`不会对数据做任何修改,所以,为`select`维护多版本,没有意义。不过,此时有个挑战, 就

select读取,是读取最新的版本呢?还是读取历史版本?

当前读。增删改,都叫做当前读,select也有可能当前读,比如:select lock in share mode(共享锁), select for update就是当前读:读取最新的记录,就

快照读:读取历史版本(一般而言),就叫做快照读。

我们可以看到,在多个事务同时删改查的时候,都是当前读,是要加锁的。那同时有select过来,如果也要读 取最新版(当前读),那么也就需加锁,这就是串行化。

但如果是快照读,读取历史版本的话,是不受加锁限制的。也就是可能并行执行!换言之,提高了效率,即 MVCC的意义所在。

快照读呢?隔离级别!就是那么,是什么决定了,select是当前读,还

那为什么要有隔离级别呢? 事务都是原子的。所以,无论如何,事务总有先有后。

只是经过上面的操作我们发现,事务从begin->CURD->commit,是有一个阶段的。也就是事务有执行前,执 行中,执行后的阶段。但,不管怎么启动多个事务,总有先有后的。

那么多个事务在执行中,CURD执行是会交织在一起的。那么,为了保证事务的“有先有后”,是不是应该让不同 的事务看到它该看到的内容,这就是所谓的隔离性与隔离级别要解决的问题。

Read View

Read View就是事务进行 快照读 操作的时候生产的 读视图 (Read View),在该事务执行的快照读的那一 刻,会生成数据库系统当前的一个快照,记录并维护平台当前活跃事务的ID(当每个事务开启时,都会被 分配一个ID, 该ID是递增的,所以最新的事务,ID值越大)

Read View 在 MySQL 源码中,就是一个类,本质是用来进行可见性判断的。 即当我们某个事务执行快照 读的时候,对该记录创建一个 Read View 读视图,把它比作条件,用来判断当前事务能够看到哪个版本的 数据,既可能是当前最新的数据,也有可能是该行记录的 undo log 里面的某个版本的数据。

下面是 ReadView 结构,简化了一下

我们在实际读取数据版本链的时候,是能读取到每一个版本对应的事务ID的,即:当前记录的 DB_TRX_ID 。

那么,我们现在手里面有的东西就有,当前快照读的 ReadView 和 版本链中的某一个记录的 DB_TRX_ID 。

整体流程

事务操作:

  • 事务4:修改name(张三) 变成name(李四)
  • 当 事务2 对某行数据执行了 快照读 ,数据库为该行数据生成一个 Read View 读视图

此时版本链是:

  • 只有事务4修改过该行记录,并在事务2执行快照读前,就提交了事务。

  • 我们的事务2在快照读该行记录的时候,就会拿该行记录的 DB_TRX_ID 去跟 up_limit_id,low_limit_id和活跃事务ID列表(trx_list) 进行比较,判断当前事务2能看到该记 录的版本。

RR 与 RC的本质区别

当前读和快照读在RR级别下的区别

select * from user lock in share mode ,以加共享锁方式进行读取,对应的就是当前读。

测试表:

测试用例1-表1:

测试用例2-表2:

  • 表1 的事务B在事务A修改age前 快照读 过一次age数据就是用例1与用例2:唯一区别仅仅
  • 而 表2 的事务B在事务A修改age前没有进行过快照读。

结论:

  • 事务中快照读的结果是很依赖该事务首次出现快照读的地方,即某个事务中首次出现快照读,决 定该事务后续快照读结果的能力
  • delete同样如此

RR 与 RC的本质区别

  • 正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同
  • 在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照及Read View, 将当前系统活 跃的其他事务记录起来
  • 此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更 新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,于是对之后的修改不可 见;
  • 可见就是即RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事 务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均
  • 而在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下 的事务中可以看到别的事务提交的更新的原因
  • 总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR隔离级别下,则是 同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。
  • 正是RC每次快照读,都会形成Read View,因而,RC才会有不可重复读问题。

posted on 2025-10-13 11:49  ycfenxi  阅读(2)  评论(0)    收藏  举报