ACID 原则:原子性、一致性、隔离性、持久性
ACID 概述
ACID 是事务必须满足的四大特性,确保数据库在并发和故障情况下保持一致性。
- Atomicity:原子性
- Consistency:一致性
- Isolation:隔离性
- Durability:持久性
Atomicity:原子性
定义:事务是不可分割的最小工作单元,要么全部成功,要么全部失败。
示例:转账
sql
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- 操作 1
UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- 操作 2
COMMIT;原子性保证:
- 两个操作都成功 → COMMIT
- 任何一个失败 → ROLLBACK,两个操作都撤销
违反原子性的后果
sql
-- 没有事务保护
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 系统崩溃
UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- 未执行结果:钱消失了(账户 1 扣款,账户 2 未收到)。
实现机制
数据库通过 Write-Ahead Logging (WAL) 实现原子性:
- 所有更改先写入日志(redo log)
- 日志持久化后才修改数据页
- 回滚时从日志恢复
Consistency:一致性
定义:事务将数据库从一个一致状态转换到另一个一致状态。
一致性约束
- 应用层约束:业务规则(如账户余额 ≥ 0)
- 数据库约束:PRIMARY KEY、FOREIGN KEY、CHECK、UNIQUE
示例:账户余额约束
sql
-- 创建约束
ALTER TABLE accounts ADD CONSTRAINT balance_positive CHECK (balance >= 0);
BEGIN;
UPDATE accounts SET balance = balance - 200 WHERE id = 1; -- 余额变成 -100
-- 违反约束,事务自动回滚
COMMIT;错误信息:
text
ERROR: new row for relation "accounts" violates check constraint "balance_positive"一致性的层次
- 数据库一致性:约束、触发器
- 应用一致性:业务逻辑(如库存不能超卖)
- 事务一致性:ACID 特性保证
示例:转账中的一致性
sql
BEGIN;
-- 检查余额(应用层一致性)
SELECT balance FROM accounts WHERE id = 1;
IF balance >= 100 THEN
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
ELSE
ROLLBACK;
END IF;Isolation:隔离性
定义:并发执行的事务之间相互隔离,一个事务的中间状态对其他事务不可见。
问题:没有隔离性
sql
-- 事务 1
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 未提交
-- 事务 2(同时执行)
SELECT balance FROM accounts WHERE id = 1; -- 看到了未提交的值(脏读)隔离级别
SQL 标准定义了四种隔离级别(下一节详细讲解):
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
示例:可重复读
sql
-- PostgreSQL 默认隔离级别:READ COMMITTED
-- MySQL InnoDB 默认:REPEATABLE READ
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- 100
-- 其他事务修改并提交
-- UPDATE accounts SET balance = 200 WHERE id = 1; COMMIT;
SELECT balance FROM accounts WHERE id = 1; -- READ COMMITTED: 200, REPEATABLE READ: 100
COMMIT;Durability:持久性
定义:事务一旦提交,其结果就是永久性的,即使系统崩溃也不会丢失。
实现机制
- Write-Ahead Log (WAL):先写日志,后写数据
- Fsync:强制刷盘,确保数据写入磁盘
- Checkpoints:定期将内存数据刷入磁盘
示例:提交后崩溃
sql
BEGIN;
INSERT INTO orders (user_id, product, amount) VALUES (1, 'Laptop', 1200);
COMMIT; -- 持久化完成
-- 系统立即崩溃
-- 重启后,数据仍然存在PostgreSQL WAL
bash
# 查看 WAL 配置
SHOW wal_level; -- replica(默认)
SHOW fsync; -- on(默认,确保持久性)MySQL InnoDB
sql
-- 查看配置
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
-- 1:每次提交都刷盘(最安全)
-- 2:每秒刷盘(性能更好,但可能丢失 1 秒数据)ACID 之间的关系
- 原子性保证事务不会部分执行
- 一致性是目标,其他三个是手段
- 隔离性处理并发事务
- 持久性保证已提交事务永不丢失
权衡
更强的 ACID 保证 = 更低的性能
- 原子性:需要额外的日志和回滚机制
- 一致性:需要约束检查
- 隔离性:需要锁或 MVCC,降低并发
- 持久性:需要频繁刷盘,影响性能
实际应用
场景 1:电商下单
sql
BEGIN;
-- 原子性:所有操作一起成功或失败
INSERT INTO orders (user_id, product, amount) VALUES (1, 'Laptop', 1200);
-- 一致性:库存不能为负
UPDATE inventory SET stock = stock - 1 WHERE product = 'Laptop' AND stock > 0;
-- 隔离性:防止超卖
-- 持久性:订单提交后不会丢失
COMMIT;场景 2:银行转账
sql
BEGIN;
-- 原子性:扣款和入账一起完成
UPDATE accounts SET balance = balance - 1000 WHERE id = 1 AND balance >= 1000;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
-- 一致性:总金额不变
-- 隔离性:其他事务看不到中间状态
-- 持久性:转账记录永久保存
COMMIT;场景 3:秒杀活动
sql
BEGIN;
-- 隔离性:防止超卖(使用 SERIALIZABLE 或悲观锁)
SELECT stock FROM products WHERE id = 1 FOR UPDATE;
IF stock > 0 THEN
UPDATE products SET stock = stock - 1 WHERE id = 1;
INSERT INTO orders (user_id, product_id) VALUES (123, 1);
COMMIT;
ELSE
ROLLBACK;
END IF;BASE:ACID 的替代方案
在分布式系统中,严格的 ACID 难以实现,常用 BASE:
- BAsically Available:基本可用
- Soft state:软状态(允许中间状态)
- Eventually consistent:最终一致性
应用场景:
- 分布式数据库(如 Cassandra)
- 微服务架构
- 高可用系统
权衡:牺牲强一致性,换取高可用和性能。
练习
原子性验证:创建转账事务,在中间插入错误,验证所有操作回滚。
一致性约束:添加 CHECK 约束,尝试违反它,观察事务自动回滚。
隔离性测试:开启两个会话,测试脏读、不可重复读。
持久性验证:提交事务后立即关闭数据库,重启后验证数据存在。
复合约束:创建外键约束,测试违反外键的事务。
性能对比:对比有无事务的插入性能(批量插入 1000 行)。
WAL 配置:查看数据库的 WAL 配置,理解持久性参数。
实际场景:设计一个订单系统,确保 ACID 四个特性都得到满足。
完成练习后,打开本章答案页核对。
下一步
理解了 ACID 原则。下一节学习隔离级别:深入理解脏读、不可重复读、幻读及其解决方案。