新手必看:MySQL 事务到底是什么?ACID + 脏读 / 幻读讲明白
一、事务的核心定义
事务(Transaction)是数据库操作的一个不可分割的执行单元,它包含了一系列的数据库操作(比如增删改查),这些操作要么全部成功执行并提交,要么全部失败并回滚,就像一个整体一样,保证了数据的一致性。
可以用一个生活例子理解:你去银行转账(A 账户转 100 元到 B 账户),这个操作包含两步:A 账户扣 100 元、B 账户加 100 元。这两步必须同时成功,才算转账完成;如果其中任意一步失败(比如 B 账户不存在),那么 A 账户扣的钱也必须恢复,这就是事务要解决的问题。
二、事务的四大特性(ACID)
这是事务最核心的特性,也是判断事务是否合规的标准:
| 特性 | 英文全称 | 含义解释 |
|---|---|---|
| 原子性(A) | Atomicity | 事务中的所有操作要么全做,要么全不做,不存在部分执行的情况。 |
| 一致性(C) | Consistency | 事务执行前后,数据库的完整性约束(比如主键唯一、外键关联)保持不变。 |
| 隔离性(I) | Isolation | 多个并发执行的事务之间相互隔离,一个事务的执行不能被其他事务干扰。 |
| 持久性(D) | Durability | 事务一旦提交成功,其修改的数据会永久保存到数据库中,即使数据库崩溃也不会丢失。 |
三、MySQL 中事务的使用方式
MySQL 中默认的存储引擎 InnoDB 支持事务(MyISAM 不支持),事务的使用主要分为两种方式:
1. 自动提交(默认模式)
MySQL 默认开启autocommit=ON,即每执行一条 DML 语句(INSERT/UPDATE/DELETE),都会自动作为一个独立的事务提交。
可以通过以下命令查看 / 修改:
-- 查看自动提交状态SELECT@@autocommit;-- 1表示开启,0表示关闭-- 临时关闭自动提交(当前会话有效)SETautocommit=0;2. 手动控制事务(核心用法)
手动控制事务需要用到三个关键命令:
START TRANSACTION/BEGIN:开启事务COMMIT:提交事务(所有操作生效)ROLLBACK:回滚事务(撤销所有未提交的操作)SAVEPOINT:设置保存点,可回滚到指定位置(而非全部)
示例:转账操作的事务实现
-- 1. 开启事务STARTTRANSACTION;-- 2. 执行转账操作(两步)-- A账户扣100元(假设A账户id=1)UPDATEaccountSETbalance=balance-100WHEREid=1;-- B账户加100元(假设B账户id=2)UPDATEaccountSETbalance=balance+100WHEREid=2;-- 3. 检查操作是否正常(可加判断逻辑)-- 如果没问题,提交事务COMMIT;-- 如果有问题(比如B账户不存在),回滚事务-- ROLLBACK;-- (可选)保存点用法STARTTRANSACTION;UPDATEaccountSETbalance=balance-100WHEREid=1;SAVEPOINTsp1;-- 设置保存点UPDATEaccountSETbalance=balance+100WHEREid=2;-- 若第二步出错,只回滚到保存点(A账户的扣款不回滚)ROLLBACKTOsp1;COMMIT;四、事务的隔离级别
多个事务并发执行时,可能会出现脏读、不可重复读、幻读等问题,MySQL 提供了 4 种隔离级别来解决这些问题,隔离级别越高,数据一致性越好,但并发性能越低。
1. 四种隔离级别(从低到高)
| 隔离级别 | 英文名称 | 解决的问题 |
|---|---|---|
| 读未提交(Read Uncommitted) | READ UNCOMMITTED | 无(会出现脏读、不可重复读、幻读) |
| 读已提交(Read Committed) | READ COMMITTED | 解决脏读(但仍有不可重复读、幻读),是 Oracle 默认级别 |
| 可重复读(Repeatable Read) | REPEATABLE READ | 解决脏读、不可重复读(仍有幻读),是 MySQL InnoDB 默认级别 |
| 串行化(Serializable) | SERIALIZABLE | 解决所有问题(但并发性能最差,相当于单线程执行) |
2. 隔离级别相关命令
-- 查看当前会话的隔离级别SELECT@@transaction_isolation;-- 查看全局隔离级别SELECT@@global.transaction_isolation;-- 设置当前会话的隔离级别(示例:设置为读已提交)SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED;-- 设置全局隔离级别SETGLOBALTRANSACTIONISOLATIONLEVELREPEATABLEREAD;3.什么是脏读、幻读、不可重复读?
我们先统一一个前提场景:数据库中有一张account表,初始数据为id=1, balance=1000,同时有两个并发执行的事务:事务 A(读写操作)、事务 B(写操作)。
脏读(Dirty Read)
定义
脏读是指一个事务读取到了另一个事务尚未提交的修改数据。这些未提交的数据就像 “脏数据”,因为后续另一个事务可能会回滚,导致当前事务读取到的是无效数据。
示例(隔离级别:读未提交 READ UNCOMMITTED)
| 时间顺序 | 事务 A(读取数据) | 事务 B(修改数据) |
|---|---|---|
| 1 | 开启事务 | 开启事务 |
| 2 | - | 将 id=1 的 balance 改为 2000(未提交) |
| 3 | 读取 id=1 的 balance,得到 2000 | - |
| 4 | - | 发现错误,执行 ROLLBACK 回滚事务 |
| 5 | 提交事务 | - |
事务 A 读取到了事务 B 未提交的 2000,但事务 B 最终回滚了,所以事务 A 读取的是 “脏数据”,这就是脏读。
不可重复读(Non-repeatable Read)
定义
不可重复读是指同一个事务内,多次读取同一数据,但得到的结果不一致。原因是在两次读取之间,另一个事务提交了对该数据的修改。
示例(隔离级别:读已提交 READ COMMITTED)
| 时间顺序 | 事务 A(重复读取) | 事务 B(修改并提交) |
|---|---|---|
| 1 | 开启事务 | - |
| 2 | 读取 id=1 的 balance,得到 1000 | - |
| 3 | - | 开启事务,将 id=1 的 balance 改为 2000,执行 COMMIT 提交 |
| 4 | 再次读取 id=1 的 balance,得到 2000 | - |
| 5 | 提交事务 | - |
事务 A 在同一个事务内两次读取同一数据,结果却不同,这就是不可重复读。它和脏读的区别是:脏读读取的是未提交的数据,不可重复读读取的是已提交的修改数据。
幻读(Phantom Read)
定义
幻读是指同一个事务内,多次执行同一个查询条件的 SQL 语句,查询结果的行数发生了变化。原因是在两次查询之间,另一个事务提交了插入 / 删除操作,导致 “凭空出现” 或 “消失” 了一些符合条件的记录,像幻觉一样。
示例(隔离级别:可重复读 REPEATABLE READ,InnoDB 默认级别可缓解但未完全解决)
| 时间顺序 | 事务 A(范围查询) | 事务 B(插入数据并提交) |
|---|---|---|
| 1 | 开启事务 | - |
| 2 | 查询 balance>500 的记录,得到 1 条(id=1) | - |
| 3 | - | 开启事务,插入一条记录:id=2, balance=1500,执行 COMMIT 提交 |
| 4 | 再次执行相同查询(balance>500),得到 2 条记录(id=1、id=2) | - |
| 5 | 提交事务 | - |
事务 A 在同一个事务内,相同查询条件的结果行数变了,这就是幻读。它和不可重复读的区别是:
不可重复读:针对单条记录的内容修改(update);
幻读:针对记录行数的变化(insert/delete)。
不同隔离级别对三种问题的解决情况
为了更清晰对比,整理成表格:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 | 有 | 有 | 有 |
| 读已提交 | 无 | 有 | 有 |
| 可重复读(MySQL 默认) | 无 | 无 | 基本解决(InnoDB 通过 MVCC 缓解,极端场景仍可能出现) |
| 串行化 | 无 | 无 | 无 |
简单打个比方:
我们用图书馆借书的场景,把三个问题分析一下:
假设你(事务 A)在图书馆查书、借书,管理员(事务 B)在整理书架、新增 / 修改书籍,二者同时进行。
脏读 → 拿了一本 “待销毁的草稿书”
场景:管理员正在整理一本新书,还没最终定稿(事务 B 未提交),只是临时放在书架上。你刚好路过,拿起这本书看了起来(事务 A 读取未提交数据)。
结果:管理员突然发现这本书内容有误,直接把它销毁了(事务 B 回滚)。你刚才看的内容,相当于 “白看了”,是无效的脏数据。
不可重复读 → 同一本书,两次看内容不一样
场景:你今天专门来图书馆看《MySQL 实战》(开启事务 A),第一次拿起来看,第 100 页写的是 “事务隔离级别”(第一次读取数据)。
过程:你中途去接水,管理员路过书架,发现这本书第 100 页印错了,当场修正并更新了馆藏记录(事务 B 修改并提交)。
结果:你回来继续看同一本书,第 100 页变成了 “ACID 四大特性”(同一事务内重复读取,内容不同)。
幻读 → 找同一类书,数量凭空变多 / 变少
场景:你要找 “所有 MySQL 相关的书”(开启事务 A),第一次统计书架上只有 3 本(范围查询结果)。
过程:你转身去登记,管理员搬来一箱新书,全是 MySQL 系列,直接上架并录入系统(事务 B 插入数据并提交)。
结果:你回来再统计一次 “所有 MySQL 相关的书”,变成了 8 本(同一事务内相同查询条件,行数变了)。就像出现了幻觉,凭空多了 5 本。
脏读:读了其他事务未提交的 “脏数据”,是最严重的读取问题;也就是读的是别人没最终确认的无效数据;
不可重复读:同一事务内多次读同一行,内容被其他事务修改并提交,核心是 “行内容变了”;也就是同一行数据被改了;
幻读:同一事务内多次读同一范围,行数被其他事务增删并提交,核心是 “行数变了”;也就是符合条件的行数被增删了。
隔离级别越高,越能解决这些问题,但数据库并发性能会越低,实际开发中需根据业务平衡一致性和性能。
五、事务的使用注意事项
只有 InnoDB 存储引擎支持事务,MyISAM 不支持,使用时需确认表的存储引擎;
事务主要用于 DML 操作(INSERT/UPDATE/DELETE),DDL 操作(CREATE/DROP/ALTER)会自动提交事务,无法回滚;
事务开启后,若长时间不提交 / 回滚,会占用数据库连接和锁资源,影响性能;
尽量缩小事务的执行范围,避免在事务中执行耗时操作(比如远程调用、复杂计算)。
总结
事务是 MySQL 中保证数据一致性的核心机制,核心特性是 ACID(原子性、一致性、隔离性、持久性);
InnoDB 支持事务,常用START TRANSACTION开启、COMMIT提交、ROLLBACK回滚来手动控制;
MySQL 默认隔离级别是可重复读(Repeatable Read),可根据业务需求调整,隔离级别越高并发性能越低。