Skip to content

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) 实现原子性:

  1. 所有更改先写入日志(redo log)
  2. 日志持久化后才修改数据页
  3. 回滚时从日志恢复

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"

一致性的层次 ​

  1. 数据库一致性:约束、触发器
  2. 应用一致性:业务逻辑(如库存不能超卖)
  3. 事务一致性: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 标准定义了四种隔离级别(下一节详细讲解):

  1. READ UNCOMMITTED
  2. READ COMMITTED
  3. REPEATABLE READ
  4. 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:持久性 ​

定义:事务一旦提交,其结果就是永久性的,即使系统崩溃也不会丢失。

实现机制 ​

  1. Write-Ahead Log (WAL):先写日志,后写数据
  2. Fsync:强制刷盘,确保数据写入磁盘
  3. 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)
  • 微服务架构
  • 高可用系统

权衡:牺牲强一致性,换取高可用和性能。

练习 ​

  1. 原子性验证:创建转账事务,在中间插入错误,验证所有操作回滚。

  2. 一致性约束:添加 CHECK 约束,尝试违反它,观察事务自动回滚。

  3. 隔离性测试:开启两个会话,测试脏读、不可重复读。

  4. 持久性验证:提交事务后立即关闭数据库,重启后验证数据存在。

  5. 复合约束:创建外键约束,测试违反外键的事务。

  6. 性能对比:对比有无事务的插入性能(批量插入 1000 行)。

  7. WAL 配置:查看数据库的 WAL 配置,理解持久性参数。

  8. 实际场景:设计一个订单系统,确保 ACID 四个特性都得到满足。

完成练习后,打开本章答案页核对。

下一步 ​

理解了 ACID 原则。下一节学习隔离级别:深入理解脏读、不可重复读、幻读及其解决方案。

回到第五章概览 · 返回SQL 学习路线

用心记录每一步成长