定义
ACID 是数据库事务的四个核心特性的缩写,分别是:
- A tomicity(原子性):事务中的所有操作要么全部成功,要么全部失败回滚
- C onsistency(一致性):事务执行前后,数据库从一个一致状态变换到另一个一致状态
- I solation(隔离性):多个并发事务之间互不干扰
- D urability(持久性):事务一旦提交,对数据的修改就是永久的
ACID 是保证数据库可靠性的基石,任何支持事务的数据库都必须实现这四个特性。
详细笔记
核心原理
一、Atomicity(原子性)
定义:事务是不可分割的最小工作单元,事务中的所有操作要么全部成功,要么全部失败回滚。
实现机制: Undo Log
sql
-- 银行转账示例
START TRANSACTION;
-- 步骤1:从账户A扣款
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 步骤2:向账户B加款
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;原子性保证:
正常流程:
1. 执行步骤1 → 记录 Undo Log
2. 执行步骤2 → 记录 Undo Log
3. COMMIT → 事务成功
异常流程(步骤2失败):
1. 执行步骤1 → 记录 Undo Log
2. 执行步骤2 → 失败!
3. ROLLBACK → 根据 Undo Log 撤销步骤1
4. 数据恢复到事务前的状态InnoDB 源码实现:
cpp
// storage/innobase/trx/trx0trx.cc
/**
* 事务回滚
*/
void trx_rollback(trx_t* trx)
{
// 1. 检查事务状态
if (trx->state == TRX_STATE_COMMITTED) {
return; // 已提交,无法回滚
}
// 2. 获取 Undo Log
undo_no_t undo_no = trx->undo_no;
// 3. 逐条撤销操作
while (undo_no > 0) {
// 从 Undo Log 读取旧值
undo_rec_t* undo_rec = trx_undo_get_undo_rec(trx, undo_no);
// 恢复数据
trx_undo_apply(undo_rec);
undo_no--;
}
// 4. 释放锁
lock_release(trx);
// 5. 标记事务为回滚状态
trx->state = TRX_STATE_ROLLED_BACK;
}关键点:
- Undo Log 记录的是逻辑日志(如"将某行的某列从值A改为值B")
- 回滚时按相反顺序执行逆向操作
- 即使系统崩溃,重启后也能根据 Undo Log 回滚未完成的事务
二、Consistency(一致性)
定义:事务执行前后,数据库必须保持一致性状态,即满足所有的约束条件(主键、外键、唯一约束、检查约束等)。
注意:一致性是事务的最终目标,原子性、隔离性、持久性都是为了保障一致性。
一致性检查示例:
sql
-- 表结构
CREATE TABLE accounts (
id INT PRIMARY KEY,
balance DECIMAL(10, 2) CHECK (balance >= 0) -- 余额不能为负
);
-- 事务
START TRANSACTION;
UPDATE accounts SET balance = balance - 200 WHERE id = 1;
-- 如果 balance 原本只有 100,现在变成 -100
COMMIT;
-- ❌ 违反 CHECK 约束,事务失败,自动回滚InnoDB 的一致性保障:
1. 约束检查:
- INSERT/UPDATE 时检查主键、外键、唯一约束
- 违反约束则语句失败
2. 崩溃恢复:
- Redo Log 重做已提交事务
- Undo Log 回滚未提交事务
- 确保重启后数据一致
3. 隔离级别:
- 通过 MVCC 或锁机制
- 避免脏读、不可重复读、幻读
- 保证并发下的一致性源码层面:
cpp
// storage/innobase/row/row0ins.cc
/**
* 插入行时检查约束
*/
dberr_t row_ins_check_constraints(
dict_index_t* index, // 索引
const dtuple_t* entry) // 要插入的数据
{
// 1. 检查 NOT NULL 约束
if (dict_col_is_not_null(index->fields[0].col) &&
dfield_is_null(&entry->fields[0])) {
return DB_CONSTRAINT_ERROR;
}
// 2. 检查唯一约束
if (dict_index_is_unique(index)) {
if (row_ins_duplicates(entry, index)) {
return DB_DUPLICATE_KEY;
}
}
// 3. 检查外键约束
if (dict_table_has_foreign_keys(index->table)) {
if (row_ins_check_foreign_keys(entry, index)) {
return DB_FK_VIOLATION;
}
}
return DB_SUCCESS;
}三、Isolation(隔离性)
定义:多个并发事务同时访问数据库时,每个事务都应该感觉不到其他事务的存在,事务之间互不干扰。
实现机制: MVCC + 锁
隔离级别回顾:
| 隔离级别 | 实现方式 | 并发度 |
|---|---|---|
| READ UNCOMMITTED | 无隔离 | 最高 |
| READ COMMITTED | MVCC(每次查询新快照) | 高 |
| REPEATABLE READ | MVCC(复用同一快照) + Next-Key Lock | 中 |
| SERIALIZABLE | 完全串行化 | 最低 |
MVCC 实现隔离性:
sql
-- 事务 A
START TRANSACTION;
SELECT * FROM users WHERE id = 1;
-- 读到: name = '张三'
-- 事务 B(并发执行)
START TRANSACTION;
UPDATE users SET name = '李四' WHERE id = 1;
COMMIT;
-- 事务 A(READ COMMITTED 级别)
SELECT * FROM users WHERE id = 1;
-- 读到: name = '李四' ✅ 看到已提交的更改
-- 事务 A(REPEATABLE READ 级别)
SELECT * FROM users WHERE id = 1;
-- 读到: name = '张三' ✅ 与第一次读取一致详见: [[MVCC]] 和 [[事务隔离级别]]
四、Durability(持久性)
定义:事务一旦提交,对数据的修改就是永久的,即使系统崩溃也不会丢失。
实现机制: Redo Log(WAL - Write Ahead Logging)
WAL 原理:
传统方式(先写数据文件):
1. 修改 Buffer Pool 中的数据页
2. 异步刷盘到数据文件
3. 如果在刷盘前崩溃 → 数据丢失! ❌
WAL 方式(先写日志):
1. 修改 Buffer Pool 中的数据页
2. 立即写入 Redo Log(顺序 I/O,很快)
3. 异步刷盘到数据文件
4. 如果崩溃 → 根据 Redo Log 恢复 ✅InnoDB Redo Log 实现:
cpp
// storage/innobase/log/log0log.cc
/**
* 事务提交时刷新 Redo Log
*/
void trx_commit(trx_t* trx)
{
// 1. 生成 Redo Log 记录
mtr_t mtr;
mtr_start(&mtr);
// 记录所有修改
for each modified page in transaction {
log_write_redo_record(&mtr, page_id, old_value, new_value);
}
// 2. 强制刷 Redo Log 到磁盘(fsync)
log_buffer_flush_to_disk();
// 3. 标记事务为已提交
trx->state = TRX_STATE_COMMITTED;
// 4. 数据页可以异步刷盘(不需要立即 fsync)
buf_flush_page_async(modified_pages);
}
/**
* 崩溃恢复时重做 Redo Log
*/
void recovery_redo()
{
// 1. 扫描 Redo Log
log_scan_from_checkpoint();
// 2. 重做所有已提交事务的修改
while (has_next_redo_record()) {
redo_rec = read_next_redo_record();
// 应用到数据页
apply_redo_record(redo_rec);
}
// 3. 回滚未提交事务(使用 Undo Log)
recovery_undo();
}Redo Log vs Undo Log:
| 特性 | Redo Log | Undo Log |
|---|---|---|
| 作用 | 重做已提交事务 | 回滚未提交事务 |
| 保证 | 持久性(D) | 原子性(A) |
| 内容 | 物理日志(某页的某偏移从A改为B) | 逻辑日志(某行的某列从A改为B) |
| 写入时机 | 事务提交时强制刷盘 | 数据修改时立即记录 |
| 清理时机 | Checkpoint 后可覆盖 | 事务提交且无其他事务需要时可清理 |
| 文件格式 | 循环写入的固定大小文件 | 存储在表空间中 |
ACID 的综合示例
完整事务流程
sql
-- 电商下单事务
START TRANSACTION;
-- 1. 创建订单
INSERT INTO orders (order_id, user_id, amount, status)
VALUES (1001, 100, 299.00, 0);
-- 2. 扣减库存
UPDATE products SET stock = stock - 1 WHERE id = 50 AND stock > 0;
-- 3. 扣减余额
UPDATE users SET balance = balance - 299.00 WHERE id = 100 AND balance >= 299.00;
-- 4. 添加订单明细
INSERT INTO order_items (order_id, product_id, quantity, price)
VALUES (1001, 50, 1, 299.00);
COMMIT;ACID 保障过程:
执行阶段:
1. 每条 SQL 执行时:
- 记录 Undo Log(用于回滚)
- 记录 Redo Log(用于恢复)
- 检查约束(保证一致性)
- 获取锁(保证隔离性)
提交阶段:
2. COMMIT 时:
- 强制刷 Redo Log 到磁盘(保证持久性)
- 释放锁
- 标记事务为已提交
异常处理:
3. 如果任何步骤失败:
- 根据 Undo Log 回滚所有操作(保证原子性)
- 释放锁
- 返回错误
崩溃恢复:
4. 如果提交后、刷盘前崩溃:
- 重启时扫描 Redo Log
- 重做已提交事务(保证持久性)
- 回滚未提交事务(保证原子性)ACID 的性能权衡
CAP 定理的启示
分布式系统中,无法同时满足:
- C: Consistency(一致性)
- A: Availability(可用性)
- P: Partition Tolerance(分区容错性)
传统关系型数据库:
- 选择 CP(一致性 + 分区容错性)
- 牺牲部分可用性(锁等待、事务阻塞)
NoSQL 数据库(如 Cassandra):
- 选择 AP(可用性 + 分区容错性)
- 牺牲强一致性(最终一致性)性能优化策略
1. 降低隔离级别
sql
-- 从高隔离到低隔离,性能提升
SERIALIZABLE → REPEATABLE READ → READ COMMITTED → READ UNCOMMITTED
-- 大多数 OLTP 系统使用 READ COMMITTED 或 REPEATABLE READ2. 缩短事务长度
sql
-- ❌ 长事务
START TRANSACTION;
SELECT ...; -- 耗时查询
UPDATE ...;
INSERT ...;
COMMIT;
-- ✅ 短事务
START TRANSACTION;
UPDATE ...;
INSERT ...;
COMMIT;3. 批量操作
sql
-- ❌ 逐行提交
FOR i IN 1..10000 LOOP
START TRANSACTION;
INSERT INTO ...;
COMMIT;
END LOOP;
-- ✅ 批量提交
START TRANSACTION;
FOR i IN 1..10000 LOOP
INSERT INTO ...;
END LOOP;
COMMIT;4. 合理配置 Redo Log
sql
-- MySQL 配置
SHOW VARIABLES LIKE 'innodb_log_file%';
-- innodb_log_file_size: Redo Log 文件大小
-- - 越大越好(减少 checkpoint 频率)
-- - 但恢复时间更长
-- innodb_flush_log_at_trx_commit: 刷盘策略
-- - 1: 每次提交都 fsync(最安全,默认)
-- - 2: 每秒 fsync 一次(性能更好,可能丢失1秒数据)
-- - 0: 由 OS 决定何时刷盘(最快,崩溃可能丢失数据)不同数据库的 ACID 实现
MySQL InnoDB
原子性: Undo Log
一致性: 约束检查 + Redo/Undo 恢复
隔离性: MVCC + 锁
持久性: Redo Log (WAL)PostgreSQL
原子性: MVCC(多版本) + WAL
一致性: 约束检查 + WAL 恢复
隔离性: MVCC(快照隔离)
持久性: WAL (Write Ahead Log)
特点:
- 使用 MVCC 实现原子性和隔离性
- 不需要 Undo Log,旧版本直接在数据页中Oracle
原子性: Undo Segments
一致性: 约束检查 + SCN(System Change Number)
隔离性: MVCC(基于 Undo)
持久性: Redo Log
特点:
- 只支持 READ COMMITTED 和 SERIALIZABLE
- 不支持 REPEATABLE READSQL Server
原子性: Transaction Log
一致性: 约束检查 + Transaction Log 恢复
隔离性: 锁机制(默认) + SNAPSHOT(可选)
持久性: Transaction Log (WAL)
特点:
- 默认使用锁机制实现隔离性
- 可选择启用 SNAPSHOT ISOLATION(MVCC)实际案例
案例一:银行转账的 ACID 保障
sql
-- 完整的转账存储过程
DELIMITER $$
CREATE PROCEDURE transfer_money(
from_account INT,
to_account INT,
amount DECIMAL(10, 2)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK; -- 原子性:失败回滚
RESIGNAL;
END;
START TRANSACTION;
-- 一致性:检查余额充足
IF (SELECT balance FROM accounts WHERE id = from_account) < amount THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '余额不足';
END IF;
-- 隔离性:锁定账户(防止并发修改)
SELECT balance FROM accounts WHERE id = from_account FOR UPDATE;
SELECT balance FROM accounts WHERE id = to_account FOR UPDATE;
-- 执行转账
UPDATE accounts SET balance = balance - amount WHERE id = from_account;
UPDATE accounts SET balance = balance + amount WHERE id = to_account;
-- 持久性:提交事务
COMMIT;
END$$
DELIMITER ;
-- ACID 保障:
-- A: 要么两个账户都更新,要么都不更新
-- C: 转账前后,总金额不变
-- I: 并发转账不会相互干扰
-- D: 提交后,即使断电也不会丢失案例二:电商超卖问题的 ACID 分析
sql
-- 问题:库存扣减不一致
-- 错误实现(缺少隔离性)
START TRANSACTION;
-- 步骤1:查询库存
SELECT stock FROM products WHERE id = 1;
-- 读到: stock = 10
-- 此时,其他 10 个事务也读到 stock = 10
-- 步骤2:扣减库存
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT;
-- 结果:10 个事务都成功,stock 变为 0
-- 但实际卖出 10 件,库存只有 10 件 → 超卖!❌
-- 正确实现(保证隔离性)
START TRANSACTION;
-- 当前读 + 排他锁
SELECT stock FROM products WHERE id = 1 FOR UPDATE;
-- 读到: stock = 10
-- 其他事务的 SELECT ... FOR UPDATE 被阻塞
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT;
-- 下一个事务才能继续
-- ACID 保障:
-- I: 隔离性保证并发安全
-- C: 一致性保证库存不为负最佳实践
- 理解 ACID 的代价
- 强一致性 = 低并发
- 根据业务需求选择合适的隔离级别
- 保持事务简短
- 事务越短,锁持有时间越短
- 减少冲突和死锁概率
- 避免在事务中进行耗时操作
- 不要在事务中调用外部 API
- 不要进行复杂的计算
- 合理配置持久性
- 金融系统:
innodb_flush_log_at_trx_commit = 1 - 日志系统:
innodb_flush_log_at_trx_commit = 2
- 监控长事务
sql
SELECT * FROM information_schema.INNODB_TRX
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60;- 测试异常场景
- 模拟崩溃恢复
- 验证数据一致性
- 确保回滚正确
关联术语
- [[MVCC]]
- [[事务隔离级别]]
- [[Redo Log]]
- [[Undo Log]]
- [[死锁]]
参考资料
- SQL 标准: ISO/IEC 9075-2:2016
- MySQL 官方文档: ACID Compliance
- InnoDB 源码:
storage/innobase/trx/trx0trx.cc - 《高性能 MySQL》第 7 章:事务与锁
- PostgreSQL 文档: ACID