什么是事务四大特性?用业务案例通俗讲透 ACID
"转账一半系统崩了怎么办?"
"两个订单同时抢最后一件库存,超卖谁背锅?"
"查账单的时候,钱还在不在?"
这些问题,本质上都在问一件事:数据库的事务靠不靠谱?
而判断一个事务靠不靠谱,业界有一套黄金标准------ACID。这四个字母,撑起了整个关系型数据库的信任基石。
今天不讲枯燥定义,我们用电商下单、转账、支付这些你每天都在写的业务场景,把 ACID 讲透。
先给一句话人话版定义
| 特性 | 英文 | 一句话解释 |
|---|---|---|
| 原子性 | Atomicity | 要么全做,要么全不做 |
| 一致性 | Consistency | 数据从一个合法状态变到另一个合法状态 |
| 隔离性 | Isolation | 多个事务互不干扰,像排队一样 |
| 持久性 | Durability | 提交成功,落地生根,断电也不丢 |
接下来,一个个拆解。
一、原子性(Atomicity):要么都成功,要么都失败
业务场景:转账
A 给 B 转账 100 元。
底层至少两步操作:
css
1. A 账户扣 100 元
2. B 账户加 100 元
如果第 1 步成功,第 2 步失败(比如服务宕机、网络抖动)会发生什么?
- A 少了 100
- B 没收到钱
- 钱"人间蒸发"了 😱
这显然不能接受。
原子性如何兜底?
数据库保证:这两步属于同一个事务。
ini
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 'A';
UPDATE account SET balance = balance + 100 WHERE id = 'B';
COMMIT;
- 两条 SQL 要么一起成功
- 只要有一条失败,数据库自动执行 ROLLBACK(回滚)
- 最终结果:钱既没少,也没多,系统回到转账前的状态
✅ 原子性 = "同生共死"
就像火箭发射:要么整体升空,要么原地不动,绝不允许"半空解体"。
二、一致性(Consistency):数据永远"说得通"
业务场景:下单扣库存
用户下单买一件商品,库存从 10 变成 9。
这里有几个"业务规则"(约束):
- 库存不能为负数
- 订单金额必须等于商品单价 × 数量
- 已支付订单必须有对应的支付记录
什么是一致性?
事务执行前后,数据库都必须满足所有业务规则和约束。
sql
START TRANSACTION;
-- 扣库存
UPDATE product SET stock = stock - 1 WHERE id = 1001;
-- 创建订单
INSERT INTO orders(...) VALUES(...);
COMMIT;
- 如果库存不足,数据库会直接报错(如 CHECK / 外键 / 应用层校验)
- 事务回滚,库存还是 10,订单也不会生成
- 不会出现"库存 -1,订单却不存在"的脏数据
✅ 一致性 = "数据始终讲道理"
⚠️ 注意一个常见误区:
一致性不是数据库自动保证的业务逻辑
而是:
-
原子性 + 隔离性 + 持久性
-
再加上你写的正确的业务代码
共同维护的结果。
换句话说:
ACID 里,A、I、D 是数据库的能力
C 是你和数据库一起守住的底线
三、隔离性(Isolation):并发不乱,各走各的
业务场景:秒杀抢最后一件商品
商品只剩 1 件,同时来了两个请求:
- 用户 A:下单
- 用户 B:下单
如果没有隔离性,可能出现这种情况:
css
A 查库存 = 1
B 查库存 = 1
A 扣库存 → 0
B 扣库存 → -1 ❌(超卖)
这就是典型的 并发问题。
隔离性做了什么?
数据库通过 锁 / MVCC(多版本并发控制) ,让多个事务互相隔离。
以 InnoDB 默认的 REPEATABLE READ 为例:
- A 的事务开启后,看到库存 = 1
- B 的事务也被正确隔离
- 其中一个事务提交后,另一个再操作时会被阻塞或失败
- 最终:只有一个订单成功,库存从 1 → 0
✅ 隔离性 = "你在操作的时候,别人看不到中间状态"
可以把它想象成:
每个人进了一个"小黑屋"操作数据,关上门干活,干完才开门让别人进来。
当然,隔离级别越高,并发性能越低,这是经典的 CAP 权衡 在事务里的体现。
四、持久性(Durability):落盘才是真安全
业务场景:支付成功
用户完成支付,页面提示"支付成功"。
这时候如果数据库服务器突然断电,数据会丢吗?
如果符合持久性,不会丢。
数据库是怎么做到的?
- 事务提交时,不只是改内存
- 还会把修改记录写入 redo log(重做日志)
- redo log 是顺序写磁盘,速度很快
- 即使断电,重启后数据库也会根据 redo log 恢复已提交的数据
perl
COMMIT → 写 redo log → 返回客户端成功
所以,哪怕 MySQL 进程崩溃、服务器掉电:
✅ 已经 COMMIT 的数据,一定存在
⚠️ 注意:
- 持久性 ≠ 立刻刷盘到数据页
- 而是:提交成功 = 数据库承诺:我能恢复
✅ 持久性 = "说了算,不算赖账"
四兄弟的关系:谁也离不开谁
很多人以为 ACID 是四个独立特性,其实它们是一个整体:
- 原子性:保证操作不残缺
- 一致性:是最终目标(数据正确)
- 隔离性:防止并发破坏一致性
- 持久性:保证结果不丢失
可以用一句话串起来:
在一个支持隔离性的环境中,通过原子性和持久性,最终达成数据的一致性。
举个完整例子:下单全流程
sql
START TRANSACTION;
-- 1. 扣库存
UPDATE product SET stock = stock - 1 WHERE id = 1001 AND stock > 0;
-- 2. 创建订单
INSERT INTO orders(order_no, user_id, amount) VALUES ('ON123', 1, 100);
-- 3. 扣账户余额
UPDATE account SET balance = balance - 100 WHERE id = 1 AND balance >= 100;
COMMIT;
在这个事务里:
- 原子性:任意一步失败,全部回滚
- 一致性:库存、订单、余额始终保持业务合法
- 隔离性:并发下单不会互相覆盖库存
- 持久性:COMMIT 成功后,哪怕宕机,订单也真实存在
常见误区澄清(面试常问)
1️⃣ 隔离性 = 串行化?
❌ 不是。
- 串行化是最高隔离级别
- InnoDB 常用的是 REPEATABLE READ
- 通过 MVCC 实现高性能并发
2️⃣ 一致性是谁保证的?
✅ 数据库保证约束(唯一、外键、NOT NULL)
✅ 开发人员保证业务逻辑正确
✅ 原子性 / 隔离性 / 持久性是基础
3️⃣ Spring 的 @Transactional 能保证 ACID 吗?
- 能保证 原子性、隔离性、持久性
- 一致性 仍然依赖你写的业务代码
- 比如:你忘了校验余额,数据库也没约束,那一致性照样崩
一句话总结
ACID 不是四个高大上的概念,而是数据库对你做出的四个承诺:
- 原子性:我不让你留烂摊子
- 一致性:我只认"讲道理"的数据
- 隔离性:你们各忙各的,互不添乱
- 持久性:我说成功,就一定能找回