跳到主要内容
SQL 算法
MySQL 事务隔离级别与并发问题分析 MySQL 事务具备原子性、一致性、隔离性和持久性。InnoDB 引擎提供四种隔离级别,默认可重复读,通过 MVCC 和锁机制处理脏读、不可重复读和幻读。不同级别在并发性能与数据安全间存在权衡,开发者需根据具体业务场景选择合适的隔离级别以确保数据正确性。
暗影行者 发布于 2026/1/5 更新于 2026/6/2 18 浏览1.什么是事务?
事务 把一组 SQL 语句 打包成为一个整体 ,在这组 SQL 的执行过程中,要么全部成功,要么全部失败。这组 SQL 语句可以是一条也可以是多条。来看一个转账的例子:
create table account( id bigint primary key AUTO_INCREMENT, Bname VARCHAR (10 ) not null , balance decimal (10 ,2 ) not null );
insert into account(Bname ,balance) values ("罗峰",1000 );
insert into account(Bname ,balance) values ("爱丽丝",1000 );
select * from account;
update account set balance = balance - 100 where bname = "罗峰";
update account set balance = balance + 100 where bname = "爱丽丝";
如果未正确控制事务,此时执行两次转账的记录应该是 800,结果却是 900,违法了事务的隔离性。
张三的账户余额减少 100,变成 900,李四的账户余额增加了 100,变成 1100,不能出现张三的余额减少而李四的余额没有增加的情况;
张三和李四在发生转账前后的总额不变,也就是说转账前张三和李四的余额总数为 1000+1000=2000,转账后他们的余额总数为 900+1100=2000;
转账后的余额结果应当保存到存储介质中,以便以后读取;
还有一点需要注意,在转账的处理过程中张三和李四的余额不能因其他的转账事件而受到干扰; 以上这四点在事务的整个执行过程中必须要得到保证,这也就是事务的 ACID 特性 。
2.事务的 ACID 特性 事务的 ACID 特性 指的是 Atomicity (原子性) ,Consistency (一致性) ,Isolation (隔离性) 和 Durability (持久性) 。
Atomicity (原子性) :一个事务中的所有操作,要么全部成功,要么全部失败,不会出现只执行了一半的情况,如果事务在执行过程中发生错误,会回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样;
Consistency (一致性) :在事务开始之前和事务结束以后,数据库的完整性不会被破坏。这表示写入的数据必须完全符合所有的预设规则,包括数据的精度、关联性以及关于事务执行过程中服务器崩溃后如何恢复;
Isolation (隔离性) :数据库允许多个并发事务同时对数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务可以指定不同的隔离级别,以权衡在不同的应用场景下数据库性能和安全;
Durability (持久性) :事务处理结束后,对数据的修改将永久的写入存储介质,即便系统故障也不会丢失。
3.为什么要使用事务? 事务具备的 ACID 特性,是我们使用事务的原因,在我们日常的业务场景中有大量的需求要用事务来保证。支持事务的数据库能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题,在使用事务过程中,要么提交,要么回滚,不用去考虑网络异常,服务器宕机等其他因素,因此我们经常接触的事务本质上是数据库对 ACID 模型的一个实现,是为应用层服务的。
4.如何使用事务
4.1 查看支持事务的存储引擎
要使用事务那么数据库就要支持事务,在 MySQL 中支持事务的存储引擎是 InnoDB,可以通过 show engines; 语句查看:
4.2 语法 # 开始一个新的事务 START TRANSACTION;
# 或 BEGIN ;
#或者 begin transaction;
# 提交当前事务,并对更改持久化保存 COMMIT ;
# 回滚当前事务,取消其更改 ROLLBACK ;
START TRANSACTION 或 BEGIN 开始一个新的事务;
COMMIT 提交当前事务,并对更改持久化保存;
ROLLBACK 回滚当前事务,取消其更改;
无论提交还是回滚,事务都会关闭
4.3 开启一个事务,执行修改后回滚 # 开启事务 START TRANSACTION;
# 在修改之前查看表中的数据 select * from bank_account;
# 张三余额减少 100 UPDATE bank_account set balance = balance - 100 where name = '张三' ;
# 李四余额增加 100 UPDATE bank_account set balance = balance + 100 where name = '李四' ;
# 在修改之后,提交之前查看表中的数据,余额已经被修改 select * from bank_account;
# 回滚事务 ROLLBACK ;
# 再查询发现修改没有生效 select * from bank_account;
4.4 开启一个事务,执行修改后提交 # 开启事务 BEGIN ;
# 在修改之前查看表中的数据 SELECT * FROM bank_account;
# 张三余额减少 100 UPDATE bank_account SET balance= balance-100 WHERE name= '张三' ;
# 李四余额增加 100 UPDATE bank_account SET balance= balance+ 100 WHERE name= '李四' ;
# 在修改之后,提交之前查看表中的数据,余额已经被修改 SELECT * FROM bank_account;
# 提交事务 COMMIT ;
# 再查询发现数据已被修改,说明数据已经持久化到磁盘 SELECT * FROM bank_account;
4.5 保存点 在事务执行的过程中设置保存点,回滚时指定保存点可以把数据恢复到保存点的状态。
# 开启事务 START TRANSACTION;
# 在修改之前查看表中的数据 SELECT * FROM bank_account;
# 张三余额减少 100 UPDATE bank_account SET balance= balance-100 WHERE name= '张三' ;
# 李四余额增加 100 UPDATE bank_account SET balance= balance+ 100 WHERE name= '李四' ;
# 余额已经被修改 SELECT * FROM bank_account;
# 设置第一个保存点 SAVEPOINT savepoint1;
# 再次执行,张三余额减少 100 UPDATE bank_account SET balance= balance-100 WHERE name= '张三' ;
# 再次执行,李四余额增加 100 UPDATE bank_account SET balance= balance+ 100 WHERE name= '李四' ;
# 余额已经被修改 SELECT * FROM bank_account;
# 设置第二个保存点 SAVEPOINT savepoint2;
# 插入一条新记录 INSERT INTO bank_account VALUES (null , '王五' , 1000 );
# 查询插入后的结果 SELECT * FROM bank_account;
# 回滚到第二个保存点 ROLLBACK TO savepoint2;
# 回滚成功后查询 SELECT * FROM bank_account;
注意:如果是没有写那个 rollback,回滚时不指定保存点,直接回滚到事务开始时的原始状态,事务关闭。
# 回滚时不指定保存点,直接回滚到事务开始时的原始状态,事务关闭 ROLLBACK ;
# 查看原始状态 SELECT * FROM bank_account;
4.6 自动/手动提交事务 默认情况下,MySQL 是自动提交事务的,也就是说我们执行的每个修改操作,比如插入、更新和删除,都会自动开启一个事务并在语句执行完成之后自动提交,发生异常时自动回滚。
show variables like 'autocommit' ;
# 设置事务自动提交 SET AUTOCOMMIT= 1 ;
# 方式一 SET AUTOCOMMIT= ON ;
# 方式二
# 设置事务手动提交 SET AUTOCOMMIT= 0 ;
# 方式一 SET AUTOCOMMIT= OFF;
# 方式二
默认情况下,MySQL 事务是自动开启、提交和回滚 的,一个事务仅包含 1 条 DML 语句(如 INSERT/UPDATE/DELETE)。
默认自动提交(autocommit=ON) :MySQL 默认将每一条 DML 语句(如 INSERT/UPDATE)作为一个独立事务 ,执行后自动提交。此时一个事务确实仅包含 1 条语句。
关闭自动提交(autocommit=OFF) :若设置 set autocommit=OFF,则执行的所有 DML 语句会默认加入同一个事务 ,直到 COMMIT/ROLLBACK 才结束。例如:
set autocommit= OFF;
UPDATE bank_account SET balance= balance-100 WHERE name= '张三' ;
UPDATE bank_account SET balance= balance+ 100 WHERE name= '李四' ;
COMMIT ;
显式控制事务(覆盖自动提交) :即使 autocommit=ON,若用 START TRANSACTION/BEGIN 显式开启事务,后续的多条 DML 语句会被合并为一个事务 ,直到 COMMIT/ROLLBACK 才结束。例如:
START TRANSACTION;
UPDATE bank_account SET balance= balance-100 WHERE name= '张三' ;
UPDATE bank_account SET balance= balance+ 100 WHERE name= '李四' ;
COMMIT ;
1. autocommit 系统变量
autocommit 是 MySQL 的系统变量,用于标识事务是否自动提交,默认值为 ON(开启自动提交)。
可通过 show variables like 'autocommit'; 查看其状态。
修改该变量时不区分大小写 ,例如 set autocommit = 0; 或 set autocommit = OFF; 均可关闭自动提交。
2. 手动提交的规则
关闭 autocommit 后,执行修改操作(如 DELETE)后,必须通过 COMMIT 提交或 ROLLBACK 回滚,才能结束事务。
若使用 START TRANSACTION/BEGIN 显式开启事务,无论 autocommit 状态如何,都必须通过 COMMIT 提交才能持久化数据。
已提交的事务无法回滚。
3. 注意事项
手动提交模式下,无需显式开启事务,执行修改操作后直接用 COMMIT/ROLLBACK 即可。
重启 MySQL 后,autocommit 会恢复为默认的自动提交状态;若需永久改为手动提交,需修改 MySQL 配置文件。
5.事务的隔离性和隔离级别
5.1 什么是隔离性 MySQL 服务可同时被多个客户端访问,每个客户端的 DML 语句以事务为单位;当不同客户端修改同一张表的同一条数据时,可能相互影响。为保证事务执行过程互不干扰,事务之间需相互隔离,这种特性即为隔离性。
5.2 隔离级别 事务的隔离需考虑'如何实现隔离、隔离程度、兼顾数据安全与性能'等问题;事务间不同程度的隔离称为隔离级别,不同隔离级别在性能和安全上做了取舍(有的侧重并发、有的侧重安全、有的二者适中)。
MySQL 的 InnoDB 引擎中,事务隔离级别有四种:
REPEATABLE READ(可重复读,MySQL 默认) :同一事务内多次读取同一数据,结果一致,解决了'脏读''不可重复读',但仍可能出现'幻读'(InnoDB 通过 MVCC 等机制优化了幻读问题)。
READ COMMITTED(读已提交) :事务只能读取其他事务已提交 的数据,解决了'脏读',但仍存在不可重复读、幻读问题,是很多数据库的默认级别。
READ UNCOMMITTED(读未提交) :事务能读取其他事务未提交 的数据,并发性能高,但会出现'脏读'(读取到无效数据)、不可重复读、幻读问题。
5.3 查看和设置隔离级别 # 全局作用域 SELECT @@GLOBAL .transaction_isolation;
# 会话作用域 SELECT @@SESSION .transaction_isolation;
默认隔离级别为 REPEATABLE-READ(可重复读)。
通过 GLOBAL|SESSION 指定作用域,语法:
SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL level| access_mode;
隔离级别(level) :
REPEATABLE READ(可重复读)
READ COMMITTED(读已提交)
READ UNCOMMITTED(读未提交)
SERIALIZABLE(串行化)
访问模式(access_mode) :
READ WRITE:事务可读写数据
READ ONLY:事务只读,不可写
1. 全局级别的隔离级别设置 SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
作用域 :对所有新连接的会话 生效(已存在的会话不受影响)。
生效时机 :当前会话后续的事务会使用该隔离级别,不影响当前正在执行的事务。
特点 :修改的是全局默认规则,新连接的会话会自动继承这个隔离级别。
2. 会话级别的隔离级别设置 SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
作用域 :仅对当前会话 生效(不影响其他会话)。
生效时机 :当前会话后续的所有事务都会使用该隔离级别,不影响当前正在执行的事务。
3. 事务级别的隔离级别设置(不指定作用域) 如果执行 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;(不写 GLOBAL/SESSION):
作用域 :仅对下一个事务 生效。
生效时机 :下一个事务执行时使用该隔离级别,之后的事务会恢复到之前的隔离级别。
核心区别 设置方式 作用域 生效范围 GLOBAL所有新会话 新连接的会话后续事务 SESSION当前会话 当前会话的后续所有事务 不指定作用域 下一个事务 仅下一个事务,之后恢复原级别
5.4 不同隔离级别存在的问题
5.4.1 READ UNCOMMITTED - 读未提交与脏读
存在问题 在 READ UNCOMMITTED(读未提交)这个隔离级别下,事务读取数据没有限制,虽然并发效率高,但会出现数据安全问题:比如事务 A 执行了 INSERT 语句插入数据,但还没执行 COMMIT(提交事务),这时候事务 B 就能读到这条'还没确定保存'的数据;如果之后事务 A 执行了回滚操作 (把刚才插入的数据撤销了),那事务 B 之前读到的这条数据就成了'无效数据' —— 相当于读了一个'不存在的、临时的脏数据',这种现象就叫'脏读'。
问题重现
客户端 A 设置全局隔离级别为 READ UNCOMMITTED :
# 设置隔离级别为 READ UNCOMMITTED 读未提交 mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)
# 查看设置是否生效 mysql> SELECT @@GLOBAL .transaction_isolation;
+
| @@GLOBAL .transaction_isolation |
+
| READ- UNCOMMITTED | # 已生效
+
1 row in set (0.00 sec)
# 查看设置是否生效 mysql> SELECT @@GLOBAL .transaction_isolation;
+
| @@GLOBAL .transaction_isolation |
+
| READ- UNCOMMITTED | # 已生效
+
1 row in set (0.00 sec)
由于 READ UNCOMMITTED 读未提交会出现'脏读'现象,在正常的业务中出现这种问题会产生非常危重后果,所以正常情况下应该避免使用 READ UNCOMMITTED 读未提交这种的隔离级别。
5.4.2 READ COMMITTED - 读已提交与不可重复读
存在问题 为了解决脏读问题,可以把事务的隔离级别设置为 READ COMMITTED ,这时事务只能读到了其他事务提交之后的数据,但会出现不可重复读的问题,比如事务 A 先对某条数据进行了查询 ,之后事务 B 对这条数据进行了修改 ,并且提交(COMMIT)事务 ,事务 A 再对这条数据进行查询时,得到了事务 B 修改之后的结果 ,这导致了事务 A 在同一个事务中以相同的条件查询得到了不同的值,这个现象叫'不可重复读'。
问题重现
在一个客户端 A 中先设置全局事务隔离级别为 READ COMMITTED:
# 设置隔离级别为 READ COMMITTED mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)
# 查看设置是否生效 mysql> SELECT @@GLOBAL .transaction_isolation;
+
| @@GLOBAL .transaction_isolation |
+
| READ- COMMITTED | # 已生效
+
1 row in set (0.00 sec)
# 查看设置是否生效 mysql> SELECT @@GLOBAL .transaction_isolation;
+
| @@GLOBAL .transaction_isolation |
+
| READ- COMMITTED | # 已生效
+
1 row in set (0.00 sec)
5.4.3 REPEATABLE READ - 可重复读与幻读
存在问题 为了解决不可重复读问题,可将事务隔离级别设置为 REPEATABLE READ,此时同一个事务中读取的数据在任何时候都是相同的结果,但仍会出现'幻读'问题:事务 A 查询一个区间的记录得到结果集 A,事务 B 向这个区间的间隙中写入一条记录并提交 ,事务 A 再次查询该区间时,会查到事务 B 新写入的记录得到结果集 B,两次查询的结果集不一致,这种现象即为'幻读'。
MySQL 的 InnoDB 存储引擎使用了 Next-Key 锁解决了大部分幻读问题。
问题重现 由于 REPEATABLE READ 隔离级别默认使用了 Next-Key 锁,为了重现幻读问题,需将隔离级回退到更新时只加了排他锁的 READ COMMITTED:
# 设置隔离级别为 READ COMMITTED mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)
# 查看设置是否生效 mysql> SELECT @@GLOBAL .transaction_isolation;
+
| @@GLOBAL .transaction_isolation |
+
| READ- COMMITTED | # 已生效
+
1 row in set (0.00 sec)
把隔离级别设置为 REPEATABLE-READ 后,在 ID 的间隙中插入新数据观察现象,比如插入 ID=4 的记录。
概念区别
1. 不可重复读
定义 :同一事务内,对同一条数据 多次查询,结果不一致(其他事务修改并提交了该数据)。
核心特征 :单条数据的内容被修改 ,导致同一事务内重复读的结果不同。
场景示例 :事务 A 查询'王五'的余额为 2000 → 事务 B 修改'王五'的余额为 1000 并提交 → 事务 A 再次查询'王五'的余额,结果变为 1000。
2. 可重复读
定义 :这是一种隔离级别(MySQL 默认),而非问题。在该级别下,同一事务内对同一条数据 的多次查询结果始终一致,解决了'不可重复读'问题。
核心作用 :保证事务内读取的单条数据的一致性,但无法完全避免'幻读'。
3. 幻读
定义 :同一事务内,对同一范围数据 多次查询,结果集的条数 / 内容不一致 (其他事务在该范围插入 / 删除了新数据)。
核心特征 :范围数据的条数变化 (插入 / 删除导致),区别于'不可重复读'的单条数据修改。
场景示例 :事务 A 查询'余额 > 1000'的记录(结果有 2 条)→ 事务 B 插入一条'余额 2000'的新记录并提交 → 事务 A 再次查询'余额 > 1000'的记录,结果变为 3 条。
5.4.4 SERIALIZABLE 串行化进一步提升事务的隔离级别到 SERIALIZABLE,此时所有事务串行执行,可以解决所有并发中的安全问题。
5.5 不同隔离级别的性能与安全 InnoDB 存储引擎事务隔离性以及相关的隔离级别是由锁和 MVCC 机制配合实现的,关于锁与 MVCC 的相关内容和原子性,持久性,一致性的相关实现原理可以参阅 MySQL 进阶中事务章节。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
SQL 美化和格式化 在线格式化和美化您的 SQL 查询(它支持各种 SQL 方言)。 在线工具,SQL 美化和格式化在线工具,online
SQL转CSV/JSON/XML 解析 INSERT 等受限 SQL,导出为 CSV、JSON、XML、YAML、HTML 表格(见页内语法说明)。 在线工具,SQL转CSV/JSON/XML在线工具,online
CSV 工具包 CSV 与 JSON/XML/HTML/TSV/SQL 等互转,单页多 Tab。 在线工具,CSV 工具包在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online