【MySQL事务和锁】回顾事务
目录
一. 回顾事务
1.1 什么是事务?什么是ACID特性?
1.2 为什么要使用事务
1.3 怎么使用事务
1.3.1.autocommit=ON时使用事务
1.3.2.通过 SET autocommit 设置自动与手动提交
1.3.3.手动提交模式下,提交或回滚事务时直接使用 commit 或 rollback
1.3.4.注意点——显式开启事务与autocommit的值无关
二. InnoDB 和 ACID 模型
三. 如何实现原子性
四. 如何实现持久性
本专题目标
- 掌握事务的概念和ACID模型
- 掌握事务的隔离级别
- 掌握锁及锁的分类
- 掌握多版本并发控制(MVCC)
- 通过UndoLog、RedoLog与隔离级别掌握ACID实现原理
一. 回顾事务
在MYSQL的课程中我们对事务做了讲解,现在来回顾一下相关知识点。
1.1 什么是事务?什么是ACID特性?
什么是事务?
事务是把一组SQL语句打包成为一个整体,在这组SQL的执行过程中,要么全部成功,要么全部失败,这组SQL语句可以是一条也可以是多余。
你可以把事务想象成一个“不可分割的操作流程”。它把多个相关的操作打包成一个整体,并且对这个整体设定了一个严格的规则:“要么全部成功完成,要么全部像没发生过一样撤销”。中间状态(比如只完成了一部分)是不允许存在的。
转账例子详解:
假设银行系统里有两个账户:
-
张三 账户有
1000
元。 -
李四 账户有
1000
元。
现在,张三要给李四转账 100
元。这个操作在计算机系统内部实际上需要两个独立的步骤:
-
从张三账户里扣除
100
元。 -
往李四账户里增加
100
元。
没有事务会怎样?
如果这两个步骤没有被当作一个整体(事务)来处理,可能会发生严重问题:
-
场景一:扣款成功,加款失败 (比如系统在第一步完成后突然崩溃)
-
结果:张三的钱被扣掉了(余额变成
900
),但李四的钱没增加(余额还是1000
)。 -
问题:
100
元钱凭空消失了! 张三损失了钱,银行数据不一致。
-
-
场景二:操作被打断 (比如另一个转账同时修改了张三或李四的余额)
-
结果:可能导致张三的余额被扣了两次,或者李四的余额增加出错,最终数据混乱。
-
事务如何解决?
银行系统会把“扣张三钱”和“加李四钱”这两个步骤打包成一个事务:
-
开始事务: 系统标记“一个转账操作开始了”。
-
执行步骤:
-
系统先在内部计算:张三余额 =
1000 - 100 = 900
。 -
系统在内部计算:李四余额 =
1000 + 100 = 1100
。 -
(注意:此时这些改动可能只在内存里,还没真正永久改数据库文件)
-
-
检查结果:
-
如果两个步骤都顺利完成(张三钱够扣,系统没崩溃等),那么系统就最终确认这个事务。这时,张三的
900
元和李四的1100
元才会被真正、永久地写入到银行的数据库文件里。 -
如果任何一个步骤失败(比如张三钱不够扣、系统在执行第二步前崩溃了),那么系统就会彻底放弃这个事务。它会把之前所有关于这次转账的内部改动全部撤销,让张三和李四的账户余额完全恢复到转账开始前的状态(张三
1000
,李四1000
),就像这次转账从来没发生过一样。
-
什么是ACID特性
下面这四点要求(原子性、一致性、隔离性、持久性)在事务的整个执行过程中必须得到保证,它们共同构成了事务的核心特征,即著名的 ACID 特性:
-
Atomicity (原子性):
-
事务被视为一个不可分割的最小工作单元。
-
事务中的所有操作,要么全部成功执行,要么全部失败撤销。
-
如果在执行过程中发生任何错误,系统会执行 回滚 (Rollback) 操作,将数据库状态精确恢复到事务开始之前的状态,如同该事务从未执行过一样。不会出现只执行了部分操作的情况。
-
-
Consistency (一致性):
-
事务的执行必须确保数据库从一个有效的状态转换到另一个有效的状态。
-
在事务开始之前和事务成功提交之后,数据库的完整性约束(如实体完整性、参照完整性、用户定义的业务规则、数据精度等)必须得到满足。
-
该特性确保写入数据库的所有数据都严格符合所有预设的规则和约束,无论事务执行过程中是否发生错误或系统崩溃。
-
-
Isolation (隔离性):
-
数据库系统允许多个事务并发执行。
-
隔离性确保并发执行的事务相互隔离,防止它们因交叉访问或修改相同数据而导致不一致的结果(如脏读、不可重复读、幻读)。
-
数据库通常提供不同的隔离级别(如读未提交、读已提交、可重复读、串行化),允许开发人员根据应用场景在性能和数据一致性保证强度之间进行权衡。后续小节将详细介绍隔离级别。
-
-
Durability (持久性):
-
一旦一个事务被成功提交 (Commit),它对数据库所做的所有更改就永久生效。
-
这些更改会被可靠地写入持久性存储介质(如硬盘)。
-
即使系统之后发生故障(如断电、崩溃),在系统恢复后,提交事务的结果也保证不会丢失,数据库能够恢复到事务提交后的正确状态。
-
1.2 为什么要使用事务
事务具备的ACID特性,也是我们使用事务的原因,在我们日常的业务场景中有大量的需求要用事务来保证。支持事务的数据库能够简化我们的编程模型,不需要我们去考虑各种各样潜在错误和并发问题,在使用事务过程中,要么提交,要么回滚,不用去考虑网络异常,服务器若机等其他因素,因此我们经常接触的事务本质上是数据库对 **ACID** 模型的一个实现,是为应用层服务的。
1.3 怎么使用事务
要使用事务那么数据库就要支持事务,在MySQL中支持事务的存储引擎是InnoDB,可以通过 show engines;语句查看:
通过以下语句可以完成对事务的控制:
- START TRANSACTION或 BEGIN开始一个新的事务;
- COMMIT提交当前事务,并对更改持久化保存;
- ROLLBACK 回滚当前事务,取消其更改;
- SET autocommit禁用或启用当前会话的默认自动提交模式,autocommit 是一个系统变量可以通过选项指定也可以通过命令行设置 --autocommit[={OFF|ON}]
在 MySQL 中,当使用 START TRANSACTION
或 BEGIN
显式开启一个事务后,必须显式执行 COMMIT
语句,该事务中的修改才会被永久提交(持久化) 到数据库中。这个要求是绝对的,不受当前会话的 autocommit
模式设置的影响。
1.3.1.autocommit=ON时使用事务
当 autocommit=ON
时,数据库的核心特性是将每条独立的 SQL 语句视为一个单独的事务,并在执行后立即自动提交。这意味着每条 DML 操作(如 INSERT
, UPDATE
, DELETE
)都会独立生效且无法回滚。
若需要将多条 DML 操作纳入同一个事务(保证原子性),必须显式地进行事务控制:
-
开始事务: 使用
START TRANSACTION
或BEGIN
语句显式启动一个新事务。 -
提交事务: 使用
COMMIT
语句提交当前事务,使其所有更改永久生效。 -
回滚事务: 使用
ROLLBACK
语句回滚当前事务,撤销其所有未提交的更改。
演示开启一个事务,执行修改后并回滚
首先我们需要创建一个表
CREATE TABLE account(
id bigint PRIMARY KEY AUTO_INCREMENT,
name varchar(255) NOT NULL,
balance decimal(10, 2) NOT NULL
);
INSERT INTO account(name, balance) VALUES('张三', 1000);
INSERT INTO account(name, balance) VALUES('李四', 1000);
select * from account;
开启事务
START TRANSACTION;
在修改之前查看表中的数据
select * from account;
张三余额减少100
UPDATE account set balance = balance - 100 where name = '张三';
李四余额增加100
UPDATE account set balance = balance + 100 where name = '李四';
在修改之后,提交之前查看表中的数据,余额已经被修改
select * from account;
回滚事务
rollback;
再查询发现修改没有生效
select * from account;
演示开启一个事务,执行修改后并回提交
开启事务
START TRANSACTION;
在修改之前查看表中的数据
select * from account;
张三条额减少100
UPDATE account set balance = balance - 100 where name = '张三';
李四余额增加100
UPDATE account set balance = balance + 100 where name = '李四';
在修改之后,提交之前查看表中的数据,余额已经被修改
select * from account;
提交事务
commit;
再查询发现数据已被修改,说明数据已经持久化到磁盘
select * from account;
默认情况下MySQL启用事务自动提交,也就是说每个语句都是一个事务,就像被 START TRANSACTION 和 COMMIT 包裹一样,不能使用 ROLLBACK 来撤销执行结果;但是如果在语句执行期间发生错误,则自动回滚;
1.3.2.通过 SET autocommit 设置自动与手动提交
查看当前的事务提交模式
show variables like 'autocommit';
ON表示自动提交模式
设置为手动提交(禁用自动提交)
-- 方式一
SET AUTOCOMMIT=0;
-- 方式二
SET AUTOCOMMIT=OFF;
在MySQL中,行注释的正确写法需要满足以下两种格式之一:
--
(两个减号后必须跟一个空格)#
(井号后直接跟注释内容)
再次查看事务提交模式
show variables like 'autocommit';
OFF表示关闭自动提交,此时转为手动提交
1.3.3.手动提交模式下,提交或回滚事务时直接使用 commit 或 rollback
我们来详细讲解一下 MySQL 中 autocommit=OFF
时事务开启的两种方式:显式和隐式。
核心概念:autocommit=OFF
的含义
当你在 MySQL 中将 autocommit
设置为 OFF
时,你本质上是在告诉数据库:“我现在要自己完全控制事务的边界(开始和结束),不要自动帮我提交每一条语句。” 这意味着数据库不会在执行每条 SQL 语句(特别是 DML 语句如 INSERT
, UPDATE
, DELETE
)后自动提交更改。更改会累积在内存中,直到你明确发出 COMMIT
或 ROLLBACK
命令。
开启事务的两种方式:
-
显式开启事务(推荐且明确)
-
这是最清晰、最推荐的方式。你可以使用
START TRANSACTION
或BEGIN
语句来明确地标记一个新事务的开始。 -
效果: 从执行
START TRANSACTION
/BEGIN
的那一刻起,直到你执行COMMIT
或ROLLBACK
,这期间执行的所有 DML 语句都属于同一个事务单元。 -
优点: 代码意图清晰,事务边界一目了然。无论之前的状态如何(比如是否有未提交的隐式事务),它都会明确启动一个新事务。这是保证代码可读性和避免意外事务嵌套的最佳实践。
-
-
隐式开启事务(由系统自动触发)
-
在
autocommit=OFF
模式下,即使你不使用START TRANSACTION
或BEGIN
,事务也可能会被隐式开启。 -
触发条件: 当你执行第一条会修改数据的 DML 语句(
INSERT
,UPDATE
,DELETE
)时,MySQL 会自动(隐式地)为你开启一个新事务。 -
效果: 这条语句及其之后执行的 DML 语句(直到遇到
COMMIT
或ROLLBACK
)都属于这个隐式开启的事务。 -
关键点:
-
仅由 DML 触发: 执行
SELECT
语句通常不会隐式开启一个新事务(除非是SELECT ... FOR UPDATE
这类锁定读)。只有实际修改数据的操作才会触发。 -
延续性: 一旦隐式事务被第一条 DML 开启,后续的 DML 语句会继续在这个事务中执行,直到你显式结束它(
COMMIT
/ROLLBACK
)。 -
结束即新开始?: 当你用
COMMIT
或ROLLBACK
结束当前事务(无论它是显式还是隐式开启的)后,系统状态会回到autocommit=OFF
。此时,下一条 DML 语句的执行将再次隐式开启一个新的事务。这就像一个循环:结束事务 -> 等待下一条 DML -> DML 触发新隐式事务。
-
-
显式开启事务(推荐且明确)
由于只要使用 START TRANSACTION 或 BEGIN 开启事务,必须要通过 COMMIT 提交才会持久化,与是否设置 SET autocommit 无关。
那么即使是autocommit=off,情况也和autocommit=on相同,所以我们不演示了,情况就和上面一模一样的
隐式开启事务(由系统自动触发),并使用rollback,commit
- 注意: autocommit=off,在没有显式事务的情况下,第一条 DML 会隐式开启一个事务,后续 DML 加入其中,直到你显式
COMMIT/ROLLBACK
。
查看事务提交模式,确定自动提交已关闭
show variables like 'autocommit';
OFF表示关闭自动提交,此时转为手动提交
查询表中现在的数据
select * from account;
张三条额减少100
UPDATE account set balance = balance - 100 where name = '张三';
注意: autocommit=off,在没有显式事务的情况下,第一条 DML 会隐式开启一个事务,后续 DML 加入其中,直到你显式
COMMIT/ROLLBACK
。
也就是说从这句update开始就隐式开启了一个事务。
在修改之后查看表中的数据,余额已经被修改
select * from account;
回滚事务
rollback;
到这里,那个隐式的事务就结束了。
再查询是被修改之后的值,发现修改没有生效
select * from account;
上一个事务已回滚,接下来重新执行更新操作,让张三余额减少100
UPDATE account set balance = balance - 100 where name = '张三';
注意: autocommit=off,在没有显式事务的情况下,第一条 DML 会隐式开启一个事务,后续 DML 加入其中,直到你显式
COMMIT/ROLLBACK
。
也就是说从这句update开始就隐式开启了一个事务。
在修改之后查看表中的数据,余额已经被修改
select * from account;
提交事务
commit;
到现在这句,一个隐式的事务就算是结束了。
再查询是被修改之后的值,说明数据已经持久化到磁盘
select * from account;
通过 SET autocommit 设置自动与自动提交
查看当前的事务提交模式
show variables like 'autocommit';
off是手动提交模式
SET AUTOCOMMIT=1; # 方式一
SET AUTOCOMMIT=ON; # 方式二
再次查看事务提交模式
show variables like 'autocommit';
ON表示自动提交模式
1.3.4.注意点——显式开启事务与autocommit的值无关
只要使用 START TRANSACTION 或 BEGIN 开启事务,必须要通过 COMMIT 提交才会持久化,与是否设置 SET autocommit 无关。
这句话揭示了 MySQL 中显式事务的核心行为规则,它独立于 autocommit
的会话设置。
可以从以下几个关键方面来理解:
-
START TRANSACTION/BEGIN
创建了一个独立的事务“作用域”:-
当你执行
START TRANSACTION
或BEGIN
时,你明确地告诉 MySQL:“接下来的操作,我要把它们当作一个原子单元来管理,请暂时忘记当前的自动提交设置”。 -
这个命令立即创建了一个新的事务上下文。所有后续的 DML 语句(
INSERT
,UPDATE
,DELETE
)都发生在这个新创建的事务内部。
-
-
显式事务“屏蔽”了
autocommit
设置:-
autocommit
是一个会话级别的设置,它控制着在没有显式事务时的默认行为。 -
一旦你进入了显式事务(通过
START TRANSACTION/BEGIN
),autocommit
的设置就被暂时“搁置”或“屏蔽”了。 此时,autocommit
是ON
还是OFF
完全不影响这个显式事务内部的提交行为。 -
在显式事务内部,系统不再关心会话的
autocommit
值。它进入了一种“纯手动模式”。
-
-
显式事务的生命周期由其明确的结束命令控制:
-
在这个显式事务“作用域”内,只有两个命令能真正结束它并决定数据的最终命运:
-
COMMIT
: 明确表示“我确认这个事务里做的所有修改,请把它们永久写入数据库(持久化)”。这是使更改生效唯一的方式。 -
ROLLBACK
: 明确表示“我放弃这个事务里做的所有修改,请撤销它们”。
-
-
没有第三条路。 只要事务是通过
START TRANSACTION/BEGIN
显式开启的,它就必须且只能通过COMMIT
或ROLLBACK
来结束。其他任何操作(包括执行更多的 DML 或某些 DDL)都不会自动提交它。
-
-
为什么与
autocommit
无关?-
autocommit=ON
的作用是:在没有显式事务的情况下,每条 DML 语句自己就是一个隐式事务并自动提交。 -
autocommit=OFF
的作用是:在没有显式事务的情况下,第一条 DML 会隐式开启一个事务,后续 DML 加入其中,直到你显式COMMIT/ROLLBACK
。 -
关键区别在于“在没有显式事务的情况下”这个前提。
START TRANSACTION/BEGIN
的出现主动打破了这个前提。它明确宣告:“我现在要自己定义一个事务范围,不要用默认规则(无论是ON
还是OFF
的默认规则)来处理我接下来的操作”。因此,在这个显式定义的范围里,规则就是“我说了算”,必须由我显式结束(COMMIT/ROLLBACK
)。
-
二. InnoDB 和 ACID 模型
ACID模型是一组数据库设计原则,强调业务数据的可靠性,MySQL的InnoDB存储引擎严格遵循ACID模型,不会因为软件崩溃和硬件故障等异常导致数据的不完整。
在ACID的实现过程中涉及到一些系统变量和相关知识点在这里先列出来,后面我们要逐步讲解:
Atomicity(原子性):原子性方面主要涉及InnoDB的事务开启与提交,我们之前做过详细讲解与回顾
- 设置 autocommit[={OFF|ON}] 系统变量,开启和禁用事务是否自动提交。
- 使用 START TRANSACTION 或 BEGIN TRANSACTION 语句开启事务;
- 使用 COMMIT 语句提交事务;
- 使用 ROLLBACK 语句回滚事务。
Consistency(一致性):一致性主要涉及InnoDB内部对于崩溃时数据保护的相关处理,相关特性包括:
- InnoDB 存储引擎的双写缓冲区 doublewrite buffer;InnoDB 存储引擎专题中已经介绍过
- InnoDB 存储引擎的崩溃恢复,备份与恢复专题中讲解。
Isolation(隔离性):隔离方面主要涉及应用于每个事务的隔离级别,相关特性包括:
- 通过 SET TRANSACTION 语句设置事务的隔离级别;
- InnoDB 存储引擎的锁,锁可以在 **INFORMATION_SCHEMA** 系统库和 **Performance Schema** 系统库中的 data_locks 和 data_lock_waits 表查看,后面的小节会详细讲解。
Durability(持久性):持久性涉及 MySQL 与特定硬件配置的交互,可能性取决于 CPU、网络和存储设备的性能,由于硬件环境比较复杂,所以无法提供固定的操作指南,只能根据实际环境进行测试得到最佳的性能,相关特性包括:
- InnoDB 存储引擎的双写缓冲区 doublewrite buffer;
- innode_flush_log_at_trx_commit 系统变量的设置;(Redo Log的刷盘策略)
- sync_binlog 系统变量的设置;(关于binlog的配置,在集群环境中使用)
- innode_file_per_table 系统变量的设置;
- 存储设备(如磁盘驱动器、SSD或RAID磁盘阵列)中的写缓冲区;
- 存储设备中电池支持的缓存。
- 运行MySQL的操作系统,特别是对 **fsync()** 系统调用的支持;
- 不间断电源UPS (uninterruptible power supply),保护所有运行MySQL服务器和数据存储设备的电力供应;
- 备份策略,例如备份的频率和类型,以及备份保留周期;
- 分布式环境中数据中心之间的网络连接。
相关内容我们将在其他优化专题;备份与恢复专题中介绍
需要重点说明的是,事务最终要保证数据的可靠和一致,也就是说ACID中的Consistency(一致性)是最终的目的,那么当事务同时满足了Atomicity(原子性), Isolation(隔离性)和Durability(持久性) 时,也就实现了一致性。
接下来我们分别讨论 MySQL 是如何实现原子性、持久性和隔离性
三. 如何实现原子性
在一个事务的执行过程中,如果多条 DML 语句顺利执行,那么结果最终会写入数据库;
如果在事务的执行过程中,其中一条 DML 语句出现异常,导致后面的语句无法继续执行或即使继续执行也会导致数据不完整、不一致,这时前面执行的语句已经对数据做了修改,如果要保证一致性,就需要对之前的修改做撤销操作,这个撤销操作称为 回滚 rollback ,如下图所示:
那么回滚操作是如何实现的呢?回滚过程中依据的是什么呢?
在InnoDB专题中介绍过UndoLog的作用和原理,我们大致回顾一下,在事务执行每个DML之前,把原始数据记录在一个日志里,做为回滚的依据,这个日志称为 Undo Log(回滚日志或撤销日志),在不考虑缓存和刷盘的条件下,执行过程如下所示:
- 当需要回滚操作时,MySQL根据操作类型,在Insert Undo链或Update Undo链中读取相应的日志记录,并反向执行修改,使数据还原,完成回滚。
- 通过 Undo Log实现了数据的回滚操作,这时就可以保证在事务成功的时候全部的SQL语句都执行成功,在事务失败的时候全部的SQL语句都执行失败,实现在原子性。
具体可以参考InnoDB 存储引擎专题中Undo Log章节
大家可以去:【InnoDB磁盘结构3】撤销表空间,Undo日志-CSDN博客
四. 如何实现持久性
提交的事务要把数据写入(持久化到)存储介质,比如磁盘。
在正常情况下大多没有问题,可是在服务器崩溃或突然断电的情况下,一个事务中的多个修改操作,只有一部分写入了数据文件,而另一部分没有写入,如果不做针对处理的话,就会造成数据的丢失,从而导致数据不完整,也就不能保证一致性。
在真正写入数据文件之前,MySQL会把事务中的所有DML操作以日志的形式记录下来,以便在服务器下次启动的时候进行恢复操作,恢复操作的过程就是把日志中没有写到数据文件的记录重新执行一遍,保证所有的需要保存的数据都持久化到存储介质中,我们把这个日志称为 Redo Log (重做日志);
生成重做日志是保证数据一致性的重要环节。
在持久化的处理过程中,还包括缓冲池、Doublewrite Buffer (双写缓冲区)、Binary Log (二进制日志)等知识点,关于InnoDB的日志生成机制以及崩溃恢复机制我们在InnoDB 存储引擎专题进行了详细讲解。
大家可以去:【InnoDB磁盘结构4】重做日志——Redo Log-CSDN博客