事务、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
相关推荐
Lhappy嘻嘻5 小时前
Java IO|File 文件操作 + 字节流 / 字符流完整笔记 + 递归删除文件实战
java·笔记·php
伊玛目的门徒5 小时前
试用leetcode之典中典 二数之和问题
java·算法·leetcode
AI-好学者6 小时前
阶段一-图数据库基础与PropertyGraph模型
数据库·rag·knowledge graph·graphrag
其实防守也摸鱼8 小时前
运维--学习阶段问题解答(1)(自测)
linux·运维·服务器·数据库·学习·自动化·命令模式
懒鸟一枚8 小时前
深入理解 Linux 内存、Swap 交换分区与分页机制的关系
java·linux·数据库
龙仔7258 小时前
SQL Server 创建只读账号完整操作(分两种场景:SSMS图形界面 + T-SQL脚本)
数据库·sql·oracle
霁月的小屋8 小时前
生产环境中的事务实践——银行系统上线记(四)
数据库
Database_Cool_9 小时前
AI 应用数据底座首选:阿里云 PolarDB 为大模型 RAG 提供一体化支撑
数据库·阿里云
玖玥拾9 小时前
C# 语言进阶(十五)C# 游戏服务端 MySQL 数据库
服务器·开发语言·网络·数据库·mysql·c#
我命由我123459 小时前
执行 Gradle 指令报错,无法将“grep”项识别为 cmdlet、函数、脚本文件或可运行程序的名称
android·java·java-ee·android studio·android jetpack·android-studio·android runtime