ACID 特性
ACID 是数据库事务的四大特性——原子性(要么全做要么全不做)、一致性(数据总是正确)、隔离性(并发事务互不干扰)、持久性(提交了就永久保存)
💸 一个转账的场景——把 ACID 四个字讲透
你在学校食堂用微信支付买了一份 15 元的午饭。你的账户扣了 15 元,食堂账户收了 15 元。整个过程不到一秒。
但如果这个过程被拆开看,会发生什么?
-- 转账 15 元
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 15 WHERE name = '你';
-- ⚡ 假设执行到这行时断电了
UPDATE accounts SET balance = balance + 15 WHERE name = '食堂';
COMMIT;
你想想——如果扣钱成功但加钱没做,会发生什么?你的 15 元凭空消失了。
这就是事务(Transaction)要防止的事情。ACID 就是事务的四大保障——让这种悲剧不会发生。
🏦 贯穿案例:微信转账 100 元
下面我们会用”张三给李四微信转账 100 元”这个场景来逐一解释 A、C、I、D 四个特性。
A —— 原子性(Atomicity)
原子性(Atomicity):事务中的所有操作要么全部成功,要么全部回滚。不存在”做了一半”的状态。
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE name = '张三';
UPDATE accounts SET balance = balance + 100 WHERE name = '李四';
COMMIT;
原子性保证:
- ✅ 两条 UPDATE 都成功 = 转账完成
- ✅ 任何一条 UPDATE 失败 = 两条都不生效(张三的钱退回来)
- ❌ 不可能出现”张三扣了 100 但李四没收到”
实现方式:Undo Log(回滚日志)
数据库在执行每条 UPDATE 之前,先把”改前值”记到 Undo Log 里:
Undo Log 中记录的原始值:
事务 T1: 张三的余额 1000 → 准备改成 900
事务 T1: 李四的余额 2000 → 准备改成 2100
如果中途出错或执行 ROLLBACK,数据库就根据 Undo Log 把数据恢复成原来的样子。
🎪 类比:舞台魔术的预备方案
魔术师准备把一个助手从箱子 A”变”到箱子 B(就像转账,从 A 扣钱到 B 加钱)。
如果变到一半(比如 A 的暗门打开了,但 B 的机关卡住了),魔术师必须能恢复到最初状态——让助手回到 A。这就是”回滚”。
原子性保证:要么大变活人成功完成,要么像没发生过一样。没有”人卡在中间”的选项。
C —— 一致性(Consistency)
一致性(Consistency):事务执行前后,数据必须满足所有的约束和规则——数据永远是”合法的”。
一致性不仅仅是数据库的事,更是应用层的事。
-- 一致性约束示例
CREATE TABLE accounts (
id INT PRIMARY KEY,
name VARCHAR(50),
balance DECIMAL(10,2) CHECK (balance >= 0) -- 余额不能为负
);
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE name = '张三';
-- 如果张三只有 50 元,这条会把余额改成 -50 → 违反 CHECK 约束
-- 事务会被回滚
UPDATE accounts SET balance = balance + 100 WHERE name = '李四';
COMMIT;
一致性的不同类型
| 类型 | 维护者 | 例子 |
|---|---|---|
| 主键唯一性 | 数据库自动保证 | 不能插入两个学号相同的学生 |
| 外键约束 | 数据库自动保证 | 成绩表中的学号必须在学生表中存在 |
| CHECK 约束 | 数据库自动保证 | 年龄必须大于 0 |
| 业务规则 | 应用程序保证 | ”转账后总金额不变”——数据库不知道这条规则 |
“转账后总金额不变”这个规则怎么保证?
这不是数据库能自动检查的——数据库只看到”张三余额 -100,李四余额 +100”,它不知道这两个操作应该”抵消”。这个逻辑需要你在应用层实现:要么在代码中保证加减一致,要么用存储过程封装转账逻辑。
📒 类比:宿舍记账本
你和室友合用一个记账本记录生活费。一致性就像记完账后,总收入 - 总支出 = 总余额——必须始终成立。
如果某天有人记账时少写了一笔(比如只记了”买奶茶 -15”,但忘了记”取现金 +100”),账本就不一致了——余额对不上。
数据库的约束(主键、外键、CHECK)就像记账本的一些铁律——比如”不能有两条一模一样的记录”。但”总收入 = 总支出 + 余额”这条规则,需要记账人自己保证。
I —— 隔离性(Isolation)
隔离性(Isolation):并发执行的事务之间互不干扰——每个事务都感觉自己是”唯一在运行”的。
没有隔离性会怎样?
-- 事务 A:张三的账户总额(余额 + 理财)
BEGIN;
SELECT balance FROM accounts WHERE name = '张三'; -- 1000
-- ⚡ 事务 B 此时转了 500 进来
SELECT investment FROM accounts WHERE name = '张三'; -- 2000
-- 结果:总额 = 1000 + 2000 = 3000 ❌(少算了 B 转的 500)
COMMIT;
如果事务 A 读到了”一半”的数据(事务 B 的部分改动),结果就是错误的。
隔离性的实现
隔离性通过两种机制实现:
- 锁(Lock)——事务 A 写数据时,事务 B 不能同时写同一行(写互斥)
- MVCC(Multi-Version Concurrency Control,多版本并发控制)——事务 A 读数据时,看到的是数据的一个”快照”,不会因为事务 B 在写而被阻塞
MVCC 的原理:每个事务看到的是数据在它开始的时刻的版本——即使其他事务在修改数据,读操作看到的始终是”自己的版本”。
📚 类比:图书馆占座和借书
锁就像占座:你在图书馆用书包占了一个座位(加锁),别人就不能坐这个座位了。你走了之后(释放锁)别人才能坐。
MVCC 就像借书系统中的”借阅记录”:很多同学同时借书(并发读写),每个人看的是自己借书证上的记录(自己的快照),互不影响。即使有同学还了一本书,你看到的结果还是”那本书没还”——直到你刷新(开始新事务)。
隔离性允许”同时学习”,但不允许”抢同一本书导致混乱”。
隔离性有不同的隔离级别(从宽松到严格),默认的隔离级别不一定是最严格的——因为越严格=并发性能越差。具体区别在下一节 事务隔离级别 中详细讲。
D —— 持久性(Durability)
持久性(Durability):一旦事务提交(COMMIT),数据修改就是永久保存的——即使后续系统崩溃、断电,数据也不会丢失。
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE name = '张三';
UPDATE accounts SET balance = balance + 100 WHERE name = '李四';
COMMIT; -- 在这一刻,结果必须永久保存
持久性 ≠ 数据已经在磁盘上
更准确地说:持久性保证的是**“即使现在断电,重启后数据也能恢复”**。
实现方式:WAL(Write-Ahead Logging,预写日志)+ Redo Log(重做日志)
- 事务提交时,先写 Redo Log(记录”把张三余额改成 900”这条操作)
- Redo Log 写成功 = 事务提交成功(哪怕真正的数据还没写到磁盘)
- 系统崩溃后重启,数据库扫描 Redo Log,把所有已提交但没写盘的操作重做一遍
⚡ 类比:写论文时突然断电
你在 Word 里写了 3000 字的论文,点击了”保存”——结果下一秒断电了。重新开机后,你发现论文还在!
为什么?因为 Word 的”保存”操作其实做了两件事:
- 先把修改写入一个隐藏的”恢复文件”(相当于 Redo Log)
- 恢复文件写入成功 = 保存成功
- 主文件可能还没写——但没关系,下次打开 Word 时,它会用恢复文件还原内容
这就是持久性的本质:日志写成功就保证数据不丢,主文件可以慢慢写。
🔗 ACID 四者的关系
ACID 不是四个独立的概念——它们紧密配合:
原子性 + 隔离性
(所有操作要么全做 (并发事务互不干扰)
要么全不做)
↓ ↓
一致性(数据始终正确)←──────────────┘
↓
持久性(提交就不丢)
- 原子性 + 隔离性 → 保证一致性
- 一致性 + 持久性 → 保证数据的可靠性
📝 小结
| 特性 | 一句话 | 实现方式 |
|---|---|---|
| Atomicity(原子性) | 全做或全不做 | Undo Log(回滚日志) |
| Consistency(一致性) | 数据始终合法 | 约束 + 应用逻辑 |
| Isolation(隔离性) | 互不干扰 | 锁 + MVCC |
| Durability(持久性) | 提交即永久 | Redo Log + WAL |
🎯 思考题:假设一个事务包含 5 条 SQL,在第 3 条执行时磁盘满了。这时候数据库应该怎么做?数据库已经做了的前两条 SQL 怎么处理?
为什么先学这个? ACID 是事务的基础。但”隔离性”有不同的”力度”——太强了性能差,太弱了数据可能出错。下一步看看事务隔离级别——如何在这两者之间做权衡。