当前位置: 首页 > article >正文

深入理解MySQL事务(万字详)

文章目录

  • 什么是事务
  • 为什么会出现事务
  • 事务的版本支持
  • 事务的提交方式
  • 事务常见操作方式
    • 正常演示 - 证明事务的开始与回滚
    • 非正常演示1 - 证明未commit,客户端崩溃,MySQL自动会回滚(隔离级别设置为读未提交)
    • 非正常演示2 - 证明commit了,客户端崩溃,MySQL数据不会在受影响,已经持久化
    • 非正常演示3 - 对比试验。begin会自动更改提交方式
    • 非正常演示4 - 证明单条 SQL 与事务的关系
  • 事务的隔离级别
    • 查看与设置隔离性
    • 读未提交(Read Uncommitted)
    • 读提交(Read Committed)
    • 可重复读(Repeatable Read)
    • 串行化(Serializable)
    • 一致性
  • 多版本并发控制MVCC
    • 3个记录隐藏列字段
    • undo日志
    • 快照
    • Read View

什么是事务

首先,我们知道MySQL是一定会在同时被多个用户访问的,那么就会发送下面这种情况

在这里插入图片描述
上述情况就会导致,同一张票被出售两次,显然不合理;

那么我们应该如何做,才可以避免上述情况呢?当满足下列条件时,即可避免

  • 买票的过程是原子的

我买票的时候别人不能影响我

  • 买票的过程不能互相影响

我正在买的时候,你来买了

  • 买完票应该是永久的

我买完票,交完钱了,你不能不给我票,直接扣我的钱,而什么也不给我,即不给票也不还钱

  • 买前,买后状态是确定的

买前、买后、买成功、买失败,不能模棱两可,一定是确定的,成不成功并不重要

那么到底什么是事务呢???

事务是一组逻辑上相关的数据库操作(DML语句),这些操作要么全部成功,要么全部失败。事务的主要目的是确保数据的完整性和一致性。例如,在一个银行转账系统中,转账操作需要同时更新两个账户的余额,这两个操作必须作为一个整体执行,否则会导致数据不一致。

  • 我对我的账号+100
  • 我对你的账号-100

单独的一条SQL并没有实际意义

一个 MySQL 数据库,可不止你一个事务在运行,同一时刻,甚至有大量的请求被包
装成事务,在向 MySQL 服务器发起事务处理请求。而每条事务至少一条 SQL ,最多很多 SQL ,这样如果大家都访问同样的表数据,在不加保护的情况,就绝对会出现问题

所以,一个完整的事务,绝对不是简单的 sql 集合,还需要满足如下四个属性,通常称为ACID属性:

  • 原子性(Atomicity):事务中的所有操作要么全部成功,要么全部失败。如果事务在执行过程中发生错误,系统会回滚到事务开始前的状态

  • 一致性(Consistency):事务执行前后,数据库必须保持一致状态。这意味着事务执行后,数据库必须满足所有的完整性约束

  • 隔离性(Isolation):多个事务并发执行时,每个事务的操作应该与其他事务隔离,防止数据不一致。MySQL提供了不同的隔离级别来控制事务的隔离程度,包括读未提交( Readuncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化( Serializable )

  • 持久性(Durability):一旦事务提交,对数据的修改就是永久的,即使系统发生故障也不会丢失

综上,事务就是在ACID四大属性的加持下,由一条、多条SQL构成的;
事务的本质(白话)是站在MySQL使用者角度,我要完成一个功能(转账),这个功能由多条SQL语句组成;

为什么会出现事务

首先,事务被 MySQL 编写者设计出来;
本质是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型,
不需要我们去考虑各种各样的潜在错误和并发问题。
因此事务本质上是为了应用层服务的,而不是伴随着数据库系统天生就有的。

事务的版本支持

首先,在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务, MyISAM 不支持。

我们可以通过以下SQL来查询
在这里插入图片描述

  • Engine: 表示存储引擎的名称
  • Support: 表示服务器对存储引擎的支持级别,YES表示支持,NO表示不支持,DEFAULT表示数据库默认使用的存储引擎,DISABLED表示支持引擎但已将其禁用
  • Comment: 表示存储引擎的简要说明
  • Transactions: 表示存储引擎是否支持事务,可以看到InnoDB存储引擎支持事务,而MyISAM存储引擎不支持事务
  • XA: 表示存储引擎是否支持XA事务
  • Savepoints: 表示存储引擎是否支持保存点

事务的提交方式

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

  • 自动提交
  • 手动提交

我们可以用下面SQL来查询当前的提交方式

在这里插入图片描述
此时Value的值为ON表示自动提交

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

在这里插入图片描述
将autocommit的值设置为1表示打开自动提交,设置为0表示关闭自动提交

事务常见操作方式

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

提前准备

为了便于演示,我们将MySQL的隔离级别设置的比较低,即成读未提交
在这里插入图片描述
现象如下
在这里插入图片描述

创建测试表,简单银行用户表

在这里插入图片描述

启动一个事务,一旦启动后,之后的所有SQL都属于同一个事务

两种启动事务的方式

在这里插入图片描述

设置savapoint s1并插入一条数据

在这里插入图片描述
在这里插入图片描述
设置savapoint s2并插入一条数据
在这里插入图片描述

在这里插入图片描述

设置savapoint s3并插入一条数据
在这里插入图片描述
在这里插入图片描述

回滚事务

回滚到s3时,s3后面的SQL就没有了;
回滚到s1时,s1后面的SQL就没有了;

在这里插入图片描述在这里插入图片描述

提交事务

提交事务后就不能回滚了

在这里插入图片描述

丢弃整个SQL,直接rollback;

在这里插入图片描述

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

在这里插入图片描述
事务在提交之前因为某些原因与MySQL断开连接,那么MySQL会自动让事务回滚到最开始,此时事务内容无效;
这也就是ACID中的A属性,原子性

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

在这里插入图片描述

非正常演示3 - 对比试验。begin会自动更改提交方式

查看autocommit的值为ON,表示事务的提交方式是自动提交

在这里插入图片描述

事务在提交之前与MySQL断开连接,那么MySQL依旧会自动让事务回滚到最开始;

证明begin操作会自动更改提交方式,不会受MySQL是否自动提交影响

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

将autocommit设置为ON,表示事务执行后自动提交

在这里插入图片描述

  • 查看autocommit的值为ON,表示事务的提交方式是自动提交
  • 左终端中直接向表中新插入一条记录,由于隔离级别是读未提交,因此在右终端中肯定能够查询到新插入的这条记录
  • 执行单条SQL后不使用commit进行提交,MySQL异常退出,这时右终端仍然可以看到之前新插入的记录了,因为单条SQL在执行后被自动提交持久化了

将autocommit设置为OFF,表示事务执行后需要手动提交

在这里插入图片描述

  • 设置autocommit的值为OFF,表示事务执行后需要手动提交
  • 左终端中直接向表中新插入一条记录,由于隔离级别是读未提交,因此在右终端中肯定能够查询到新插入的这条记录
  • 执行单条SQL后不使用commit进行提交,MySQL异常退出,这时右终端看不到之前新插入的记录了,因为单条SQL在执行后异常退出,MySQL断开连接则会自动进行回滚操作

结论:我们之前一直都在使用单SQL事务,只不过autocommit默认是打开的,因此单SQL事务执行后自动就被提交了

事务的隔离级别

举个例子:

你妈妈给你说:你要么别学,要学就学到最好。至于你怎么学,中间有什么困难,你妈妈不关心。那么你的学习,对你妈妈来讲,就是原子的。那么你学习过程中,很容易受别人干扰,此时,就需要将你的学习隔离开,保证你的学习环境是健康的。

  • 数据库中,为了保证事务执行过程中尽量不受干扰,就有了一个重要特征:隔离性
  • 数据库中,允许事务受不同程度的干扰,就有了一种重要特征:隔离级别

隔离级别

  • 读未提交(Read Uncommitted):事务可以读取其他事务未提交的数据,可能会导致脏读、不可重复读和幻读。

  • 读已提交(Read Committed):事务只能读取其他事务已提交的数据,避免了脏读,但可能会导致不可重复读和幻读。

  • 可重复读(Repeatable Read):MySQL默认的隔离级别,确保同一事务中多次读取同一数据时,结果一致。避免了脏读和不可重复读,但可能会有幻读。

  • 串行化(Serializable):最高的隔离级别,强制事务串行执行,避免了脏读、不可重复读和幻读,但性能最差。

查看与设置隔离性

查看隔离级别

  • 查看全局的隔离级别
SELECT @@global.transaction_isolation;

在这里插入图片描述

  • 查看会话隔离级别
SELECT @@session.transaction_isolation;

在这里插入图片描述
也可以省略掉session

SELECT @@transaction_isolation;

在这里插入图片描述

设置隔离级别

语法:

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED 
| READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
  • 设置会话隔离级别

在这里插入图片描述
只会影响当前会话的隔离级别,不会影响全局的,即使新起会话也不会影响

  • 设置全局隔离级别

在这里插入图片描述

设置全局隔离级别会影响后续的新会话,但当前会话的隔离级别没有发生变化,如果要让当前会话的隔离级别也改变,则需要重启会话

读未提交(Read Uncommitted)

事务A所作的修改在没有提交之前,事务B就已经能够看到了

在这里插入图片描述
一个事务在执行中,读到另一个执行中事务的更新(或其他操作)但是未commit的数据,这种现象叫做脏读

读提交(Read Committed)

事务A所作的修改在没有提交之前,事务B不能看到
只有当事务A提交后,事务B才能看到修改后的数据
在这里插入图片描述
在B事务没有commit之前,执行过程中,两个相同的select查询得到了不同的数据,这种现象叫做不可重复读

不可重复读是个问题吗??

可重复读(Repeatable Read)

A事务修改数据,B事务并不能查到
在这里插入图片描述
只有A事务commit后,B事务也查不到
在这里插入图片描述
只有当双方都commit后,B事务才可以查到
在这里插入图片描述

  • 在可重复读隔离级别下,一个事务在执行过程中,相同的select查询得到的是相同的数据,这就是可重复读
  • 一个事务在执行过程中,相同的select查询得到了新的数据,如同出现了幻觉,这种现象叫做幻读

串行化(Serializable)

将隔离级别都设置为串行化,双方查询互不影响
在这里插入图片描述
此时事务A要删除一个数据,但是阻塞在了这里
在这里插入图片描述
但是事务B的查询并不受影响
在这里插入图片描述
当事务Bcommit后,离开事务A就恢复,立马执行SQL对表进行修改
在这里插入图片描述

  • 串行化是事务的最高隔离级别,多个事务同时进行读操作时加的是共享锁,因此可以并发执行读操作,但一旦需要进行写操作,就会进行串行化,效率很低,几乎不会使用

结论:

在这里插入图片描述

一致性

多版本并发控制MVCC

数据库的并发场景

  • 读-读并发:不存在任何问题,也不需要并发控
  • 读-写并发:有线程安全问题,可能会存在事务隔离性问题,可能遇到脏读、幻读、不可重复读
  • 写-写并发:有线程安全问题,可能会存在两类更新丢失问题
  1. 每个事务都有自己的事务ID,ID的大小决定着,事务到来的顺序;
  2. mysqld可能会在一个时间范围中处理多个事务,事务也有自己的声明周期,mysqld要对多个事务进行管理;

3个记录隐藏列字段

在这里插入图片描述

当我们创建下面一个表
在这里插入图片描述
该记录不仅包含name和age字段,还包含上述三个隐藏字段

undo日志

MySQL 中的一段内存缓冲区,用来保存日志数据的

  • redo log:重做日志,用于MySQL崩溃后进行数据恢复,保证数据的持久性
  • bin log:逻辑日志,用于主从数据备份时进行数据同步,保证数据的一致性
  • undo log:回滚日志,用于对已经执行的操作进行回滚,保证事务的原子性

快照

现在有一个事务ID为10的事务,要将刚才插入学生表中的记录的学生姓名“张三”改为“李四”

  • 事务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)

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

在这里插入图片描述
这样就形成了一个基于历史版本的链表

上面的每个版本,我们可以称之为快照

当前读 、快照读

当前读:读取最新的记录,就叫做当前读。
快照读:读取历史版本,就叫做快照读。

事务在进行增删查改的时候,并不是都需要进行加锁保护:

  • 事务对数据进行增删改的时候,操作的都是最新记录,即当前读,需要进行加锁保护

  • 事务在进行select查询的时候,既可能是当前读也可能是快照读,如果是当前读,那也需要进行加锁保护,但如果是快照读,那就不需要加锁,因为历史版本不会被修改,也就是可以并发执行,提高了效率,这也就是MVCC的意义

Read View

Read View就是事务进行 快照读 操作的时候生产的读视图 (Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID

其源码

class ReadView {
	// 省略...
private:
	/** 高水位:大于等于这个ID的事务均不可见*/
	trx_id_t m_low_limit_id;
	
	/** 低水位:小于这个ID的事务均可见 */
	trx_id_t m_up_limit_id;
	
	/** 创建该 Read View 的事务ID*/
	trx_id_t m_creator_trx_id;
	
	/** 创建视图时的活跃事务id列表*/
	ids_t m_ids;
	
	/** 配合purge,标识该视图不需要小于m_low_limit_no的UNDO LOG,
	* 如果其他视图也不需要,则可以删除小于m_low_limit_no的UNDO LOG*/
	trx_id_t m_low_limit_no;
	
	/** 标记视图是否被关闭*/
	bool m_closed;
	
	// 省略...
};

在这里插入图片描述
也就是

  • id < m_up_limit_id || id == m_creator_trx_id
    事务id小于m_up_limit_id(已提交)或事务id为创建该Read View的事务的id,则可见

  • id >= m_low_limit_id
    事务id大于等于m_low_limit_id,则不可见

  • m_ids.empty()
    事务id位于m_up_limit_id和m_low_limit_id之间,并且活跃事务id列表为空(即不在活跃列表中),则可见


http://www.kler.cn/a/518509.html

相关文章:

  • iOS 集成ffmpeg
  • SocketCAN
  • python深入SQLAlchemy使用详解
  • GitLab配置免密登录和常用命令
  • 【第一天】零基础入门刷题Python-算法篇-数据结构与算法的介绍(持续更新)
  • 【ComfyUI】python调用生图API,实现批量出图
  • Zemax 非序列模式下的颜色检测器和颜色混合
  • Windows10安装MySQL找不到MSVCR120.dll和MSVCP120.dll问题解决
  • Python脚本自动删除C盘临时文件夹:scoped_dir* 开头的文件夹
  • 汽车敏捷开发:项目经理如何精准跟进项目流程
  • VMware虚拟机安装macOS11
  • C语言练习(23)
  • 开源软件协议介绍
  • 代码随想录 二叉树 test 2
  • 2025美赛数学建模B题 管理可持续旅游业保姆级教程讲解|模型讲解
  • 第19篇:python高级编程进阶:使用Flask进行Web开发
  • 基于Netty的自定义协议栈设计与编解码技术解析
  • 基于Flask的天猫美妆销售数据分析系统的设计与实现
  • PortSwigger靶场练习---跨站点请求伪造:CSRF vulnerability with no defenses没有防御措施的 CSRF 漏洞
  • 导出地图为pdf文件
  • [极客大挑战 2019]Upload1
  • 假期学习【Java程序】的实施方案
  • C#标准Mes接口框架(持续更新)
  • 三分钟简单了解一些HTML的标签和语法_02
  • 技术总结:FPGA基于GTX+RIFFA架构实现多功能SDI视频转PCIE采集卡设计方案
  • Linux 命令行网络连接指南