MySQL 事务到底是什么?ACID 四大特性与隔离级别怎么理解?

事务到底是什么?为什么数据库需要事务?

1. 为什么需要事务?

假设现在有一个转账业务。

用户 A 给用户 B 转:

text 复制代码
100 元

业务逻辑:

text 复制代码
A 账户 -100
    ↓
B 账户 +100

对应 SQL:

sql 复制代码
UPDATE account
SET balance = balance - 100
WHERE id = 1;

UPDATE account
SET balance = balance + 100
WHERE id = 2;

正常情况下:

text 复制代码
A:1000 → 900

B:500 → 600

没有问题。

但是假设第一条 SQL 执行成功:

text 复制代码
A:1000 → 900

然后系统突然:

text 复制代码
宕机

第二条 SQL 没有执行。

最终:

text 复制代码
A 少了 100

B 没收到 100

钱凭空消失了。

这显然是不允许的。

因此我们希望:

这两条 SQL 要么全部成功,要么全部失败。

这就是事务最核心的作用。

2. 什么是事务?

事务可以简单理解成:

把多个数据库操作作为一个整体执行。

例如:

text 复制代码
事务开始

    ↓

A -100

    ↓

B +100

    ↓

事务提交

如果中途失败:

text 复制代码
事务开始

    ↓

A -100

    ↓

发生异常

    ↓

回滚

数据库会把之前的操作撤销。

于是:

text 复制代码
A:仍然是 1000

B:仍然是 500

所以事务可以简单记成:

text 复制代码
全部成功
或者
全部失败

3. MySQL 中怎么使用事务?

最基本的写法:

sql 复制代码
START TRANSACTION;

UPDATE account
SET balance = balance - 100
WHERE id = 1;

UPDATE account
SET balance = balance + 100
WHERE id = 2;

COMMIT;

其中:

text 复制代码
START TRANSACTION

表示:

text 复制代码
开启事务

然后执行多条 SQL。

最后:

sql 复制代码
COMMIT;

表示:

text 复制代码
提交事务

只有提交以后,这个事务才正式完成。

如果中间出现问题:

sql 复制代码
ROLLBACK;

就可以:

text 复制代码
回滚事务

也就是撤销本次事务中的修改。

4. 事务最经典的四个特性:ACID

数据库事务通常需要满足四个特性:

text 复制代码
A
Atomicity 原子性

C
Consistency 一致性

I
Isolation 隔离性

D
Durability 持久性

简称:

text 复制代码
ACID

5. Atomicity:原子性

一个事务中的操作,要么全部执行成功,要么全部失败。

例如:

text 复制代码
A -100
B +100

不能出现:

text 复制代码
A -100 成功

B +100 失败

而是:

text 复制代码
情况一:

A -100
B +100
↓
一起提交

或者:

text 复制代码
情况二:

A -100
B +100 失败
↓
全部回滚

所以原子性可以简单理解成:

事务是一个不可再拆分的整体。

6. Consistency:一致性

转账之前:

text 复制代码
A = 1000
B = 500

总金额 = 1500

转账以后:

text 复制代码
A = 900
B = 600

总金额 = 1500

虽然两个账户的数据变了:

text 复制代码
1000 → 900

500 → 600

但是:

text 复制代码
总金额

没有变化。

这说明数据库从一个:

text 复制代码
合法状态

转换到了另一个:

text 复制代码
合法状态

这就是一致性。

可以简单理解成:

事务执行前后,数据库都应该满足业务规则和数据约束。

例如:

text 复制代码
余额不能小于 0

订单金额不能为负数

用户名必须唯一

这些都属于一致性的一部分。

7. 一致性是不是完全靠 MySQL 保证?

不是。

数据库可以帮我们保证:

text 复制代码
主键约束

唯一约束

外键约束

事务机制

但是业务一致性很多时候还需要:

text 复制代码
应用代码

例如:

text 复制代码
账户余额不能为负数

可能需要业务自己判断:

sql 复制代码
UPDATE account
SET balance = balance - 100
WHERE id = 1
  AND balance >= 100;

所以:

一致性其实是事务最终想达到的目标,而原子性、隔离性、持久性等机制共同帮助实现一致性。

8. Isolation:隔离性

这个概念和:并发事务有关。

假设现在:

text 复制代码
事务 A

正在修改一个账户:

text 复制代码
balance = 1000

然后事务 A:

text 复制代码
减去 100

变成:

text 复制代码
balance = 900

但是:

text 复制代码
事务 A 还没有提交

这时候:

text 复制代码
事务 B

也来查询余额。

问题来了:

事务 B 应不应该看到 900?

如果看到:

text 复制代码
900

结果事务 A 最后:

text 复制代码
ROLLBACK

那么余额又变回:

text 复制代码
1000

事务 B 刚才看到的:

text 复制代码
900

其实是一个:

text 复制代码
从未真正提交的数据

这就会产生并发问题。

所以事务之间需要一定程度的:

text 复制代码
隔离

这就是:

一个事务的执行不应该随意受到其他并发事务的影响。

9. Durability:持久性

一个事务一旦提交成功,它的结果就应该被永久保存。

例如:

text 复制代码
COMMIT

已经返回成功。

那么即使之后:

text 复制代码
MySQL 崩溃

服务器重启

系统断电

已经提交的数据也应该能够恢复。

例如:

text 复制代码
A = 900

B = 600

不能重启以后:

text 复制代码
A 又变回 1000

B 又变回 500

在 InnoDB 中,持久性和:

text 复制代码
redo log

有非常重要的关系。

10. ACID 怎么一起理解?

还是转账:

text 复制代码
A -100
B +100

原子性

text 复制代码
两个操作要么全部成功
要么全部失败

一致性

text 复制代码
转账前后数据都满足业务规则

隔离性

text 复制代码
并发事务之间不能随意互相干扰

持久性

text 复制代码
事务提交成功后
结果不能因为系统崩溃而消失

11. 为什么并发事务会产生问题?

数据库不可能:

text 复制代码
一次只允许一个用户操作

真实系统中通常会有:

text 复制代码
事务 A
事务 B
事务 C
事务 D
...

同时执行。

并发能够提高系统性能。

但同时也会产生一些问题。

最经典的三个:

text 复制代码
脏读

不可重复读

幻读

理解事务隔离级别,首先就得理解它们。

12. 什么是脏读?

假设:

text 复制代码
事务 A

执行:

sql 复制代码
UPDATE account
SET balance = 900
WHERE id = 1;

但是:

text 复制代码
还没有 COMMIT

这时候事务 B:

sql 复制代码
SELECT balance
FROM account
WHERE id = 1;

如果读到了:

text 复制代码
900

那么:

text 复制代码
事务 B

读取到了:

text 复制代码
事务 A 尚未提交的数据

接着事务 A:

sql 复制代码
ROLLBACK;

余额又恢复:

text 复制代码
1000

那么事务 B 刚才读到的:

text 复制代码
900

就是一个无效数据。

text 复制代码
Dirty Read  脏读

读到了别人还没提交的数据。

13. 什么是不可重复读?

假设事务 A:

sql 复制代码
SELECT balance
FROM account
WHERE id = 1;

第一次查询:

text 复制代码
1000

然后事务 B:

sql 复制代码
UPDATE account
SET balance = 900
WHERE id = 1;

COMMIT;

事务 B 修改并提交了。

接着事务 A:

sql 复制代码
SELECT balance
FROM account
WHERE id = 1;

第二次查询:

text 复制代码
900

于是:

text 复制代码
同一个事务

第一次读:1000

第二次读:900

两次结果不一样。

这就是:

text 复制代码
Non-Repeatable Read 不可重复读

同一个事务里,同一行数据读两次,值变了。

14. 什么是幻读?

假设事务 A:

sql 复制代码
SELECT *
FROM user
WHERE age > 18;

第一次查出来:

text 复制代码
10 条

然后事务 B 插入:

sql 复制代码
INSERT INTO user(...)
VALUES (...);

这条新数据:

text 复制代码
age = 20

然后事务 B:

sql 复制代码
COMMIT;

事务 A 再次执行:

sql 复制代码
SELECT *
FROM user
WHERE age > 18;

结果:

text 复制代码
11 条

突然多出了一条。

好像出现了幻影,所以叫:

text 复制代码
Phantom Read  幻读

同一个范围查询执行两次,结果集中的行数发生变化。

15. 不可重复读和幻读有什么区别?

不可重复读

重点是:

text 复制代码
某一行的数据变了

例如:

text 复制代码
第一次:

id = 1
balance = 1000

第二次:

id = 1
balance = 900

还是同一行,只是值变了。

幻读

重点是:

text 复制代码
结果集中出现了新行
或者少了某些行

例如:

text 复制代码
第一次:10 条

第二次:11 条

16. 怎么解决这些并发问题?

最简单粗暴的方法:

text 复制代码
所有事务一个一个执行
text 复制代码
事务 A
   ↓
执行完

事务 B
   ↓
执行完

事务 C

这样肯定不会产生并发问题,但是性能太差。

所以数据库需要在并发性能和数据隔离之间做平衡。

于是就出现了:

text 复制代码
事务隔离级别

17. SQL 标准四种隔离级别

SQL 标准定义了四种经典事务隔离级别:

text 复制代码
READ UNCOMMITTED

READ COMMITTED

REPEATABLE READ

SERIALIZABLE

隔离程度:

text 复制代码
低
↓
READ UNCOMMITTED
↓
READ COMMITTED
↓
REPEATABLE READ
↓
SERIALIZABLE
↓
高

通常:

text 复制代码
隔离性越高

意味着:

text 复制代码
并发限制越强

因此一般来说:

text 复制代码
性能和并发能力可能越低

18. READ UNCOMMITTED

text 复制代码
读未提交

一个事务可以读取另一个事务尚未提交的数据。

例如:

text 复制代码
事务 A:

UPDATE balance = 900

还没 COMMIT

事务 B:

text 复制代码
已经可以看到 900

因此它可能产生:

text 复制代码
脏读

同时:

text 复制代码
不可重复读

幻读

也都可能发生。

所以:

text 复制代码
READ UNCOMMITTED

隔离级别非常低。

实际业务中通常很少使用。

19. READ COMMITTED

text 复制代码
读已提交

只能读取其他事务已经提交的数据。

例如:

text 复制代码
事务 A
修改 balance = 900
但没有提交

事务 B:

text 复制代码
看不到 900

等事务 A:

text 复制代码
COMMIT

以后,事务 B 才能看到。

脏读被解决了。

但是,不可重复读 和 幻读仍然可能出现。

20. READ COMMITTED 为什么会不可重复读?

假设事务 A:

text 复制代码
第一次查询:

balance = 1000

事务 B:

text 复制代码
修改为 900
↓
COMMIT

事务 A 再查:

text 复制代码
balance = 900

因为:

text 复制代码
事务 B 已经提交

所以 READ COMMITTED 允许事务 A 看到最新已提交数据。

于是:

text 复制代码
第一次 1000

第二次 900

产生:

text 复制代码
不可重复读

所以可以简单理解:

text 复制代码
READ COMMITTED
↓
每次读取当前已经提交的最新版本

21. REPEATABLE READ

text 复制代码
可重复读

这是 InnoDB 中非常重要的隔离级别,也是 MySQL InnoDB 默认使用的事务隔离级别。

它希望做到:

同一个事务中,对同一数据进行多次一致性读取,看到的结果保持一致。

例如事务 A:

text 复制代码
第一次:

balance = 1000

然后事务 B:

text 复制代码
修改:

balance = 900

并 COMMIT

事务 A 再查询:

text 复制代码
1000

所以:

text 复制代码
第一次:1000

第二次:1000

实现:

text 复制代码
Repeatable Read 可重复读

22. MySQL 是不是把旧数据复制了一份?

看到这里就会产生一个问题:

事务 B 明明已经update并且commit了。

为什么事务 A 还能看到:

text 复制代码
旧版本 1000

这就是接下来非常重要的:

text 复制代码
MVCC
Multi-Version Concurrency Control

多版本并发控制。

可以先简单理解成:

text 复制代码
同一行数据
   ↓
可能存在多个历史版本

不同事务根据:

text 复制代码
自己的可见性规则

决定:

text 复制代码
应该看到哪个版本

所以事务 A 和事务 B:

text 复制代码
同时查询同一条数据

可能看到不同版本。

23. REPEATABLE READ 能不能解决幻读?

如果按照 SQL 标准的经典定义理解,它主要解决:

text 复制代码
脏读

不可重复读

幻读仍然是更高隔离级别需要处理的问题。

但是 InnoDB 的实际实现更加复杂。

InnoDB 在:

text 复制代码
快照读

中通过:

text 复制代码
MVCC

可以让同一个事务中的一致性读取维持一个稳定的快照,因此很多普通 SELECT 场景不会看到新插入的数据。

而对于:

text 复制代码
当前读

又会结合:

text 复制代码
Next-Key Lock

等锁机制控制范围内的并发修改。

所以不能简单说:

text 复制代码
MySQL RR 一定会出现幻读

或者:

text 复制代码
MySQL RR 完全不存在任何幻读问题

更加准确地说:

InnoDB 的 REPEATABLE READ 通过 MVCC 和锁机制,在大量实际场景中对幻读进行了处理。

之后再具体讲锁机制和MVCC

24. SERIALIZABLE

text 复制代码
SERIALIZABLE 串行化

尽可能让并发事务表现得像按照顺序一个一个执行。

这样:

text 复制代码
脏读

不可重复读

幻读

都能够被避免。

但是代价就是:

text 复制代码
并发能力降低

因为很多操作之间需要:

text 复制代码
等待

加锁

阻塞

所以虽然隔离程度最高:

text 复制代码
SERIALIZABLE

但实际系统中通常不会无脑使用。

25. 四种隔离级别放在一起

可以简单总结:

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED 可能 可能 可能
READ COMMITTED 避免 可能 可能
REPEATABLE READ 避免 避免 SQL 标准下仍可能
SERIALIZABLE 避免 避免 避免

26. 怎么查看当前事务隔离级别?

MySQL 中可以执行:

sql 复制代码
SELECT @@transaction_isolation;

例如可能返回:

text 复制代码
REPEATABLE-READ

表示当前使用:

text 复制代码
REPEATABLE READ

也可以设置当前 Session:

sql 复制代码
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

或者:

sql 复制代码
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

27. Spring 中的事务其实也是这些东西

如果做 Java / Spring Boot 开发,经常会写:

java 复制代码
@Transactional
public void transfer(
        Long fromId,
        Long toId,
        BigDecimal amount) {

    accountService.deduct(fromId, amount);

    accountService.add(toId, amount);
}

这里:

text 复制代码
@Transactional

本质上就是让:

text 复制代码
deduct()

add()

在一个数据库事务中执行。

正常:

text 复制代码
两个都成功
   ↓
COMMIT

发生异常:

text 复制代码
中途失败
   ↓
ROLLBACK

所以理解 MySQL 事务之后,再看:

text 复制代码
Spring @Transactional

就不会觉得它是什么神秘的东西。

Spring 只是:

text 复制代码
帮助我们管理事务边界

真正的数据事务能力最终仍然来自数据库

28. 为什么 @Transactional 有时候不回滚?

很多 Java 开发者会遇到:

java 复制代码
@Transactional
public void save() {
    ...
}

但是最终发现:

text 复制代码
事务没有回滚

常见原因包括:

text 复制代码
方法内部 this 调用

异常被自己 catch 掉

异常类型不符合默认回滚规则

方法没有经过 Spring 事务代理

这和Spring AOP有关。

不过这属于 Spring 事务层面的内容,不是 MySQL 事务本身。

相关推荐
SelectDB1 小时前
Doris 直查 Paimon 索引:快手湖上向量检索的共建与落地
大数据·数据库·数据分析
风123456789~1 小时前
【Oracle专栏】全局 && 本地复合索引 实验
数据库·oracle
1314lay_10071 小时前
通过Sql Server创建EXCEL文件:master..xp_cmdshell
数据库·excel
zzzll11112 小时前
用 DeepSeek Harness 手搓一个自己的 Agent
数据库
名字还没想好☜2 小时前
Spring @EventListener 事件驱动解耦实战:同步转异步、事务绑定与顺序控制
java·数据库·后端·python·spring
del2 小时前
第十周技术博客
数据库
1314lay_10073 小时前
C#调用Sql Server存储过程,并且使用传入表结构
数据库·经验分享·笔记·sqlserver·c#
anxiao_m3 小时前
2026大文件传输软件推荐,从速度安全多维度客观测评
数据库·文件传输·传输
IT大白鼠3 小时前
MySQL 分布式集群系列 · 第八篇(收官)——NDB 集群面试高频题 +架构总结与未来演进
分布式·mysql·面试