事务、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
相关推荐
IvorySQL1 分钟前
PostgreSQL 日报| GiST 索引扫描可见性缺陷(8 月 3 日)
数据库·人工智能·postgresql·开源
Lyra_Infra3 分钟前
Java 应用启动脚本 JDK 路径适配优化文档
java·shell
xiaoxiangsiyan4 分钟前
LNMP + Redis Sentinel 高可用架构部署手册(续)
运维·网络·数据库·redis·缓存·架构·sentinel
刘婉晴8 分钟前
【Web漏洞】SQL 注入实战技巧
前端·数据库·sql
CodeHackerBhx40 分钟前
Spring Boot 4 防重复提交:从接口幂等到 Redis 分布式锁的完整实践
java·数据库·spring boot·redis·分布式
xlxxy_1 小时前
外部系统调用SAP接口遇到的一些报错
开发语言·数据库·sap·abap
八角.。1 小时前
方法参数与Debug按键
java·开发语言·jvm
BerryS3N1 小时前
Java在人工智能与大模型时代的深度演进:从工程落地、高性能计算到企业级Agent与RAG架构实战指南
java·人工智能·架构
麦聪聊数据1 小时前
连锁零售全域数据管控(下):API 化服务输出,释放全域数据业务价值
数据库
蔬菜_1 小时前
前端转全栈-day2(修饰符)
java