MySQL 事务详解

MySQL 事务详解本文将会介绍 MySQL 的事务 ACID 特性和 MySQL 事务控制流程的语法 并介绍事务并发处理中可能出现的异常情况 比如脏读 幻读 不可重复读等等 最后介绍事务隔离级别

前言

要么全都带走,要么一无所有。 —- 卡牌大师 崔斯特

在实际业务场景中,如何保证操作的完整性是一个重要的议题,依次执行一系列逻辑强关联的操作,如果在中途发生了错误,就很有可能导致数据的错乱。

设想一下在 ATM 取钱的场景,当我们取出一千元的时候,ATM 会在清点完成后一次性吐出一千元,而不是分十次每次吐出一百元,这就是为了保证操作的完整性,要么完整的取走一千元,扣除余额,要么一分钱都没有取走,余额不变,而不会出现中途机器故障导致数据不一致的情况。这样的一次完整操作叫做事务 transaction,一个事务中的所有操作要么全部成功执行,要么完全不执行。

本文将会介绍 MySQL 的事务 ACID 特性和 MySQL 事务控制流程的语法,并介绍事务并发处理中可能出现的异常情况,比如脏读、幻读、不可重复读等等,最后介绍事务隔离级别。

关于实现事务隔离性的锁和 MVCC,将会在之后的文章进行介绍。

ACID 特性

事务处理是一种对必须整批执行的 MySQL 操作的管理机制,在事务过程中,除非整批操作全部正确执行,否则中间的任何一个操作出错,都会回滚 (Rollback) 到最初的安全状态以确保不会对系统数据造成错误的改动。

之前的文章中我们提到过,MySQL 5.5 之后,默认的存储引擎从 MyISAM 替换成了 InnoDB,这其中的一个重要原因就是因为 InnoDB 支持事务,我们用 SHOW ENGINES 来看一下 MySQL 中对各种存储引擎的描述。
在这里插入图片描述
事务最重要的四个特性通常被称为 ACID 特性
A - Atomicity 原子性: 一个事务是一个不可分割的最小单位,事务中的所有操作要么全部成功,要么全部失败,没有中间状态。原子性主要是通过事务日志中的回滚日志(undo log)来实现的,当事务对数据库进行修改时,InnoDB 会根据操作生成相反操作的 undo log,比如说对 insert 操作,会生成 delete 记录,如果事务执行失败或者调用了 rollback,就会根据 undo log 的内容恢复到执行之前的状态。






C - Consistency 一致性: 事务执行之前和执行之后数据都是合法的一致性状态,即使发生了异常,也不会因为异常引而破坏数据库的完整性约束,比如唯一性约束等。

I - Isolation 隔离性: 每个事务是彼此独立的,不会受到其他事务的执行影响,事务在提交之前对其他事务不可见。隔离性通过事务的隔离级别来定义,并用锁机制来保证写操作的隔离性,用 MVCC 来保证读操作的隔离性,将在下文详细介绍。

D - Durability 持久性: 事务提交之后对数据的修改是持久性的,即使数据库宕机也不会丢失,通过事务日志中的重做日志(redo log)来保证。事务修改之前,会先把变更信息预写到 redo log 中,如果数据库宕机,恢复后会读取 redo log 中的记录来恢复数据。

事务控制语法

MySQL 事务控制有几个重要节点,分别是事务的开启,提交,回滚和保存点。

开启事务代表事务开始执行,语句为 START TRANSACTION 或者 BEGIN,提交事务代表将事务中的所有更新都写到磁盘的物理数据库,事务正常执行结束,语句为 COMMIT,如果发生异常需要回滚,语句为 ROLLBACK。要注意的是,一旦事务已经提交,就不能回滚了,因此,在代码执行过程中捕获到异常的时候需要直接执行 rollback 而不是 commit。

比如 A 向 B 转账 100 元的事务:

// 正常执行,提交 BEGIN; # 开启事务 UPDATE account_balance SET balance = balance - 100.00 WHERE account_name = 'A'; UPDATE account_balance SET balance = balance + 100.00 WHERE account_name = 'B'; COMMIT; # 提交事务 // 发生异常,回滚 BEGIN; # 开启事务 UPDATE account_balance SET balance = balance - 100.00 WHERE account_name = 'A'; UPDATE account_balance SET balance = balance + 100.00 WHERE account_name = 'B'; ROLLBACK; # 事务回滚 

在复杂场景中,有时我们不需要全盘回滚整个操作,而是分批执行,回滚到某个节点就好了,相当于是在一个大事务下嵌套了若干个子事务,在 MySQL 中可以使用保留点 SAVEPOINT 来实现。

BEGIN; insert into user_tbl (id) values (1) ; insert into user_tbl (id) values (2) ; ROLLBACK; # 1,2 都没有写入 BEGIN; insert into user_tbl (id) values (1) ; SAVEPOINT s1; insert into user_tbl (id) values (2) ; ROLLBACK TO s1; # 回滚到保留点 s1, 因此 1 成功写入,2 被回滚, 最终结果为 1 RELEASE SAVEPOINT s1; # 释放保留点 

顺便提一下,事务有隐式事务(自动提交)和显示事务(必须手动提交)两种,MySQL 默认为隐式事务,会进行自动提交,通过 autocommit 参数来控制。

# 查看变量 SHOW VARIABLES LIKE 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ # 开启自动提交(默认) SET autocommit = 1; # 关闭自动提交 SET autocommit = 0; 

在自动提交状态下,如果没有显示的开启事务,那每一条语句都是一个事务,系统会自动对每一条 sql 执行 commit 操作。使用 BEGIN 或 START TRANSACTION 开启一个事务之后,自动提交将保持禁用状态,直到使用 COMMIT 或 ROLLBACK 结束事务之后,自动提交模式会恢复到之前的状态。

关于事务还有另一个参数 completion_type,默认取值为 0 (NO_CHAIN)

# 查看变量 SHOW VARIABLES LIKE 'completion_type'; +-----------------+----------+ | Variable_name | Value | +-----------------+----------+ | completion_type | NO_CHAIN | +-----------------+----------+ 

事务并发异常

在实际产线环境下,可能会存在大规模并发请求的情况,如果没有妥善的设置事务的隔离级别,就可能导致一些异常情况的出现,最常见的几种异常为脏读(Dirty Read)幻读(Phantom Read)不可重复读(Unrepeatable Read)

脏读

脏读指一个事务访问到了另一个事务未提交的数据,如下过程:

  1. 假设 a 的值为 1,事务 2 把 a 改为 2,此时事务还未提交
  2. 在这个时候,事务 1 读取 a,读得 a 的值为 2,事务 1 读取完成
  3. 结果事务 2 回滚了对 a 的修改(或者是未 commit),于是 a 的值变回 1
  4. 这就导致事实上 a 的值为 1,但是事务 1 取得的结果为 2,所以事务 1 读到了脏数据,发生脏读
    在这里插入图片描述

不可重复读

不可重复读指一个事务多次读取同一数据的过程中,数据值 内容 发生了改变,导致没有办法读到相同的值,描述的是针对同一条数据 update/delete 的现象,如下过程:

  1. 事务 1 读取 a,此时 a = 1
  2. 此时事务 2 将 a 修改为 2,并成功提交,改动生效
  3. 事务 1 又一次读取 a,此时 a = 2
  4. 事务 1 在同一个事务里面两次读取同一个值,数据值内容却发生了改变,发生不可重复读
    在这里插入图片描述

幻读

幻读指一个事务多次读取同一数据的过程中,全局数据(如数据行数)发生了改变,仿佛产生了幻觉,描述的是针对全表 insert/delete 的现象,如下过程:

  1. 事务 1 第一次读取数量,得到 10 条数据
  2. 此时事务 2 插入了一条数据并成功提交,改动生效,数据变成 11 条
  3. 事务 1 再次读取数量,得到 11 条数据,对事务 1 而言莫名其妙的多了一条,好像产生幻觉了一样,发生幻读
    在这里插入图片描述

或者是另一种场景,比如对于有唯一性约束的字段(如 id),发生如下过程:

  1. 事务 1 要插入 id = 5 的记录,先查询数据库,发现不存在 id = 5 的数据,可以正常插入。
  2. 这时候事务 2 插入了一条数据 id = 5。
  3. 事务 1 插入 id = 5 时,发现报错唯一性冲突,对事务 1 来讲就好像见了鬼了,我刚刚明明检查过没有,怎么这时候又有了。
    MySQL 事务详解

事务隔离级别

串行化的事务处理方式当然是最安全的,但是串行无法满足数据库高并发访问的需求,作为妥协,有时不得不降低数据库的隔离标准来换取事务的并发能力,通过在可控的范围内牺牲正确性来换取效率的提升,这种权衡通过事务的隔离级别来实现。

数据库有 4 种事务隔离级别,由低到高依次为 读未提交 Read Uncommitted读已提交 Read Committed可重复读 Repeatable Read串行化 Serializable

  1. 读未提交 Read Uncommitted
    允许读取未提交的内容,这种级别下的查询不会加锁,因此脏读、不可重复读、幻读都有可能发生。

  2. 读已提交 Read Committed
    只允许读取已提交的内容,这种级别下的查询不会发生脏读,因为脏数据属于未提交的数据,所以不会被读取,但是依然有可能发生不可重复读和幻读。

  3. 可重复读 Repeatable Read (MySQL 的默认隔离级别)
    使用行级锁来保证一个事务在相同查询条件下两次查询得到的数据结果一致,可以避免脏读和不可重复读,但是没有办法避免幻读。

    需要特殊注意的是,Innodb 在 Repeatable Read 下通过 MVCC 提供了稳定的视图,因此 Innodb 的 RR 隔离级别下是不会出现上述幻读异常中的第一个场景的,但第二个场景还是会出现。

  4. 串行化 Serializable
    使用表级锁来保证所有事务的串行化,可以防止所有的异常情况,但是牺牲了系统的并发性。

四种隔离级别中上述三种异常情况的容忍度如下(✅ 代表允许,❌ 代表禁止):

脏读 不可重复读 幻读
读未提交 Read Uncommitted
读已提交 Read Committed
可重复读 Repeatable Read
串行化 Serializable

查看隔离级别的命令为

SHOW VARIABLES LIKE 'transaction_isolation'; # 或者 SELECT @@global.tx_isolation, @@tx_isolation; 
# 将当前会话的隔离级别设为读未提交 SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; # 将全局的隔离级别设为读未提交 SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 

结语

本文简单介绍了 MySQL 事务的语法和 ACID 特性,以及事务并发处理中可能出现的异常情况和为了防止这些异常而设计的事务隔离级别。有兴趣的朋友可以尝试在两个不同的 MySQL 客户端来模拟四种隔离级别下三种异常的发生情况,在之后的文章中,会继续深入探讨 MySQL 是如何实现隔离级别的。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/229219.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月16日 下午5:11
下一篇 2026年3月16日 下午5:12


相关推荐

  • 较为详细的MUSIC算法原理及MATLAB实现

    较为详细的MUSIC算法原理及MATLAB实现DOA 估计算法 DOA DirectionOfA 波达方向定位技术主要有 ARMA 谱分析 最大似然法 熵谱分析法和特征分解法 特征分解法主要有 MUSIC 算法 ESPRIT 算法 WSF 算法等 MUSIC MultipleSign 算法 即多信号分类算法 由 Schmidt 等人于 1979 年提出 MUSIC 算法是一种基于子空间分解的算法 它利用信号子空间

    2026年3月20日
    3
  • 国外网页设计网站推荐_十大国外设计网站

    国外网页设计网站推荐_十大国外设计网站  国内的大学网站都设计得千篇一律,古老的表格式框架布局,直来直去的排版,还有悲剧的是好多大学的网址只能在IE中浏览,其它浏览器里就错乱了。今天这篇文章和大家分享20个国外大学网站设计欣赏,其实大学网站一样可以设计得很艺术,很精美。BiolaUndergradBatesCollegeMiddleburyCollegeWarnerPacificColleg…

    2025年5月26日
    4
  • pd.notnull

    pd.notnull

    2022年6月10日
    31
  • Spring Boot – JPA配置使用[通俗易懂]

    1.JPA是什么JPA顾名思义就是JavaPersistenceAPI的意思,是JDK5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。2.使用JPA的优势2.1标准化JPA是JCP组织发布的JavaEE标准之一,因此任何声称符合JPA标准的框架都遵循同样的架构,提供相同的访问API,这保证了基于JPA开发的…

    2022年4月3日
    120
  • c winform 浏览器调用chrome内核

    c winform 浏览器调用chrome内核privatevoidF Load objectsender EventArgse varsettings newCefSettin CefSharp Cef Initialize settings browser newChromiumW https aaa com auth account chinav

    2026年3月19日
    2
  • 一阶惯性滤波特点_一阶惯性环节仿真

    一阶惯性滤波特点_一阶惯性环节仿真由文章(二)可知,二次平均法改善了一次平均法滤除低频干扰时超调的影响,但仍然未能完全滤除低频干扰。因此,通过改变反馈控制系统参数结合平均法是一个不错的选择,仿真如图:通过改变比例系数,可以实现一定的滤波效果,此方法是通过降低响应参数的方式实现一级滤波,通过平均法实现二级滤波,仿真结果如下图:文章《一阶惯性传感器的快速跟踪性能实现》中,最终传递函数为:本例中,传递函数为启动性能与文章《一阶惯性传感器的快速跟踪性能实现》中接近,且较大地滤除了高频、低频干扰可见,该控制

    2022年10月4日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号