事务、ACID与隔离

什么是事务

假设A给B转100元,即:

sql 复制代码
UPDATE account SET money = money - 100 WHERE id = 1;
UPDATE account SET money = money + 100 WHERE id = 2;

正常情况:

复制代码
A:1000 B:1000
↓
A:900 B:1100

但如果执行到一半:

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

成功后服务器崩了,第二条没执行,结果:

复制代码
A:900
B:1000

钱凭空消失。

于是,数据库提出:

Transaction(事务)

sql 复制代码
BEGIN;

UPDATE account SET money = money - 100 WHERE id = 1;
UPDATE account SET money = money + 100 WHERE id = 2;

COMMIT;

要求:

复制代码
要么都成功要么都失败

这就是事务

ACID

复制代码
A Atomicity
C Consistency
I Isolation
D Durability

A:Atomicity(原子性)

意思:

复制代码
事务不可分割

例如转账,扣钱与加钱必须看成一个整体,不能扣钱成功加钱失败

原子性是谁实现的?

undo log

理解:

复制代码
执行成功保留
执行失败回滚

D:Durability(持久性)

意思:

复制代码
事务提交后数据不能丢

例如:

复制代码
COMMIT;

返回成功。

用户已经看见:

复制代码
转账成功

这时候断电,你不能说:

复制代码
刚刚骗你的

所以提交成功的数据必须存在。是谁保证?

答案:

redo log
复制代码
undo log 
↓
Atomicity

redo log 
↓
Durability

Consistency(一致性)

一致性:

复制代码
事务执行前后数据满足业务规则

例如银行总金额:

复制代码
A:1000 B:1000
总计2000

转账后:

复制代码
A:900 B:1100
总计仍然2000

一致性没有被破坏。

如果:

复制代码
A:900 B:1000

总计:

复制代码
1900

一致性被破坏。

一致性是:

复制代码
Atomicity+Isolation+Durability

共同保证的最终结果。

Isolation(隔离性)

假设事务A:

sql 复制代码
BEGIN;
UPDATE account SET money = 500 WHERE id = 1;

但还没提交。

此时事务B:

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

B看到 500 还是原值?

这里就出现:

并发问题

因为多个事务同时执行,如果不控制,数据会乱。

所以,数据库提出:

Isolation

隔离性

目标:事务之间互不干扰

并发会带来什么问题

1.脏读

事务A:

sql 复制代码
BEGIN;
UPDATE user SET money = 500;

未提交。

事务B:

sql 复制代码
SELECT money;

看到 500

然后A回滚,实际值:100

B读到了不存在的数据。

这就是脏读

2.不可重复读

事务A:

sql 复制代码
BEGIN;

第一次查询:

sql 复制代码
SELECT money;

结果:100

事务B:

sql 复制代码
UPDATE money=200;
COMMIT;

事务A再次查询:

sql 复制代码
SELECT money;

结果:200

同一个事务,同一个SQL,两次结果不同,这就是不可重复读

3.幻读

事务A:

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

结果:10条

事务B:

sql 复制代码
INSERT ...

新增一条。

事务A再次查询:

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

结果:11条

突然多出一条,像出现幻觉。这就是幻读

隔离级别

为了解决上面问题,SQL标准提出四个隔离级别。

Read Uncommitted

允许读未提交。

问题:脏读、不可重复读、幻读全部可能发生,所以几乎不用。

Read Committed(RC)

只读已提交数据。

解决:

复制代码
脏读

但仍有:

复制代码
不可重复读
幻读

这是:

  • Oracle默认
  • PostgreSQL常用模式

Repeatable Read(RR)

解决:

复制代码
脏读
不可重复读

MySQL默认。

注意:很多数据库:RR仍有幻读,但InnoDB比较特殊

为什么 RR 解决脏读和不可重复读?

例如事务A:

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

结果:100

事务B:

sql 复制代码
UPDATE account SET money=200 WHERE id=1;
COMMIT;

事务A再次查询:

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

为什么 RC 看到200,RR 还能看到100?

如果数据库真的把数据改掉了,那么:

  • 磁盘里已经是200
  • Buffer Pool里也是200

为什么事务A还能看到100?

这里就出现一个非常关键的问题:

数据库是不是只有一份数据?

答案:

复制代码
不是

这是 MVCC 的起点。

假设有一条记录:

复制代码
id=1
money=100

事务A开始:

sql 复制代码
BEGIN;

时间:

复制代码
T1

事务B开始:

sql 复制代码
BEGIN;

时间:

复制代码
T2

事务B修改:

sql 复制代码
UPDATE account 
SET money=200 
WHERE id=1;

然后提交。

现在数据库里:

复制代码
money=200

问题:事务A为什么还能看到100?

如果数据库只有:

复制代码
money=200

这一份数据,显然做不到。

所以:InnoDB干了一件很聪明的事:

不覆盖旧数据

而是:

复制代码
版本1:money=100
版本2:money=200

同时存在。

这就叫:

Multi Version

多版本。

所以,MVCC全称:

复制代码
Multi-Version Concurrency Control

多版本并发控制。

可以先把 MVCC 理解成 Git,例如Git里:

复制代码
commit1
commit2
commit3

都存在,你不会因为 commit3 出现,就看不到 commit1

MVCC也一样,数据库会保留:旧版本+新版本

然后决定:

复制代码
哪个事务能看到哪个版本

Serializable

最严格,事务串行执行。

问题:性能极差,因此几乎不用。

隔离级别 脏读 不可重复读 幻读
RU
RC
RR 理论有
Serializable
相关推荐
shepherd11132 分钟前
吞吐量提升 10 倍:高并发大批量数据处理任务的架构演进与性能调优
java·后端·架构
ClouGence2 小时前
Oracle 数据同步为什么会出现数据不一致?长事务是常被忽略的原因
数据库·后端·oracle
plainGeekDev3 小时前
单例模式 → object 声明
android·java·kotlin
用户298698530144 小时前
Java 实现 Word 文档文本与图片提取的方法
java·后端
飞将5 小时前
从零实现数据库(2)——HashIndex + IndexManager
数据库
SimonKing5 小时前
铁子,IntelliJ IDEA 2026.1.3来了,升不升?
java·后端·程序员
咖啡八杯16 小时前
GoF设计模式——策略模式
java·后端·spring·设计模式
用户128526116021 天前
我把祖传Java项目重构后,接口响应从3s砍到了200ms,只改了这几行代码
java
Linsk1 天前
组件 = 模板 + 业务逻辑
java·前端·vue.js