ACID 特性
问题
事务的 ACID 特性分别是什么?数据库是如何保证每一个特性的?
答案
ACID 概述
| 特性 | 英文 | 含义 | 实现机制(InnoDB) |
|---|---|---|---|
| 原子性 | Atomicity | 事务要么全做,要么全不做 | undo log |
| 一致性 | Consistency | 事务前后数据满足约束 | 由 A + I + D 共同保证 |
| 隔离性 | Isolation | 并发事务互不干扰 | MVCC + 锁 |
| 持久性 | Durability | 提交后数据不丢失 | redo log + WAL |
一致性是目标,其他三个是手段。原子性保证操作不会只做一半,隔离性保证并发不会互相干扰,持久性保证提交的数据不会丢失 —— 三者共同保障数据从一个一致状态转换到另一个一致状态。
原子性(Atomicity)
含义:事务中的操作要么全部执行成功,要么全部回滚,不存在执行了一半的中间状态。
实现机制 —— undo log:
undo log 记录了数据修改前的值(逆操作):
| 操作 | undo log 记录 |
|---|---|
| INSERT | 记录新行的主键,回滚时 DELETE |
| DELETE | 记录完整行数据,回滚时 INSERT |
| UPDATE | 记录被修改列的旧值,回滚时还原 |
-- 示例:转账事务
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- undo log: UPDATE accounts SET balance = (原值) WHERE id = 1
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- undo log: UPDATE accounts SET balance = (原值) WHERE id = 2
-- 如果第二条 UPDATE 失败 → ROLLBACK
-- InnoDB 按照 undo log 反向还原两条记录
ROLLBACK;
一致性(Consistency)
含义:事务执行前后,数据库从一个一致状态转换到另一个一致状态。所有数据完整性约束(主键、外键、唯一约束、CHECK 约束、业务规则)不被破坏。
保证方式:
| 层面 | 手段 |
|---|---|
| 数据库层面 | 原子性(不会半做)+ 隔离性(不被干扰)+ 持久性(不丢数据) |
| 约束层面 | PRIMARY KEY、UNIQUE、FOREIGN KEY、NOT NULL、CHECK |
| 应用层面 | 业务逻辑正确性(如转账总金额不变)需要应用代码保证 |
原子性、隔离性、持久性是数据库提供的技术保障。一致性更偏向"最终结果的正确性",既需要数据库的技术保障,也需要应用层的业务逻辑正确。例如,数据库可以保证转账不会中断,但"等额转入转出"的规则需要应用代码来保证。
隔离性(Isolation)
含义:多个事务并发执行时,每个事务的执行不受其他事务的干扰,就像单独执行一样。
实现机制:
| 机制 | 作用 |
|---|---|
| MVCC | 读操作不加锁,通过快照实现读写不冲突 |
| 锁机制 | 写写冲突通过行锁、间隙锁等解决 |
| 隔离级别 | 定义不同程度的隔离强度 |
持久性(Durability)
含义:事务一旦提交,对数据的修改就是永久的,即使系统崩溃也不会丢失。
实现机制 —— redo log + WAL:
WAL(Write-Ahead Logging,预写日志):
核心思想:先写日志,再写数据。
- 数据修改先写入 redo log(顺序写磁盘,很快)
- 再修改内存中的数据页(Buffer Pool)
- 脏页异步刷回磁盘(随机写,较慢)
- 如果在脏页刷盘前崩溃,通过 redo log 恢复
为什么用 redo log 而不直接写数据文件?
| 直接写数据文件 | 写 redo log |
|---|---|
| 随机 IO(数据页分散在磁盘各处) | 顺序 IO(日志追加写) |
| 每次写一个完整页(16KB) | 只写修改部分(几十字节) |
| 慢 | 快 |
redo log vs binlog
| 对比项 | redo log | binlog |
|---|---|---|
| 层级 | InnoDB 引擎层 | MySQL Server 层 |
| 内容 | 物理日志(页的修改) | 逻辑日志(SQL 语句) |
| 大小 | 固定大小,循环写 | 追加写,无大小限制 |
| 作用 | 崩溃恢复(Crash Recovery) | 主从复制、数据恢复 |
详细内容见 MySQL 日志系统。
ACID 实现总结
常见面试问题
Q1: ACID 分别是什么?数据库如何保证?
答案:
- 原子性(A):事务全做或全不做。InnoDB 通过 undo log 实现,回滚时反向执行 undo log 恢复数据
- 一致性(C):事务前后数据满足所有约束。本质上是其他三个特性的最终目标,加上约束机制共同保证
- 隔离性(I):并发事务互不干扰。InnoDB 通过 MVCC(读写不冲突)和 锁(写写冲突)实现
- 持久性(D):提交后数据不丢失。InnoDB 通过 redo log + WAL 机制实现
Q2: undo log 和 redo log 的区别?
答案:
| 对比项 | undo log | redo log |
|---|---|---|
| 目的 | 保证原子性(回滚) | 保证持久性(崩溃恢复) |
| 内容 | 数据修改前的旧值 | 数据修改后的新值 |
| 写入时机 | 修改数据前 | 事务提交时 |
| 生命周期 | 事务结束后可能保留(MVCC 需要) | 循环覆盖写 |
两者配合:提交时 redo log 保证修改不丢,回滚时 undo log 恢复原值。
Q3: 什么是 WAL?为什么需要它?
答案:
WAL(Write-Ahead Logging)= 先写日志,再写数据。
原因:
- 性能:redo log 是顺序写(追加),数据文件是随机写。顺序写比随机写快几个数量级
- 安全:redo log 写入成功后,即使数据页还没刷盘,崩溃后也能通过 redo log 恢复
- 批量化:内存中的脏页可以积累后批量刷盘,减少磁盘 IO 次数
Q4: 一致性和隔离性有什么关系?
答案:
一致性是最终目标,隔离性是达成一致性的手段之一。
如果没有隔离性,并发事务可能读到中间状态的数据(脏读),或前后两次读到不同数据(不可重复读),导致基于这些数据做出的操作违反业务一致性。
隔离级别越高,一致性保障越强,但并发性能越低。实际应用中需要根据业务场景选择合适的隔离级别。
Q5: 如果系统在事务提交后、数据刷盘前崩溃,数据会丢吗?
答案:
不会丢失。原因:
- 事务提交时,redo log 已经写入磁盘(
innodb_flush_log_at_trx_commit = 1时) - 系统重启后,InnoDB 执行崩溃恢复:
- 扫描 redo log,找到已提交但未刷盘的修改
- 重放这些修改到数据页
- 同时用 undo log 回滚未提交事务的修改
这就是 WAL 的核心价值 —— redo log 落盘 = 数据安全。