SQL事务的ACID特性包括原子性、一致性、隔离性和持久性,它们是关系型数据库可靠处理并发读写与故障恢复的基础。原子性确保一组操作要么全部成功要么全部失败,一致性保证数据库从一个合法状态变到另一个合法状态,隔离性使并发事务互不干扰,持久性让已提交的数据不会因系统崩溃而丢失。这些特性并非数据库魔法,而是由日志系统、锁机制与存储引擎协同实现。

原子性(Atomicity)与undo日志
原子性要求事务内的所有修改作为一个不可分割的单元。如果事务在执行到一半时发生错误或者用户主动回滚,数据库必须撤销已经产生的所有变更。实现这一目标的核心结构是undo日志,它在数据页被修改前记录旧值,用于回滚与多版本读。
以MySQL InnoDB为例,每当修改一行记录,引擎会先写undo记录再改内存页。若事务回滚,就按undo链反向恢复。下面伪代码展示了一个转账事务的原子性控制思路:
START TRANSACTION; UPDATE account SET balance = balance - 100 WHERE id = 1; UPDATE account SET balance = balance + 100 WHERE id = 2; -- 若任意一步失败,执行 ROLLBACK 利用undo日志还原 COMMIT;
如果第二条语句因主键冲突失败,数据库通过undo日志把第一条语句扣减的金额退回,从而避免钱凭空消失。没有原子性,应用层就要自己补偿,复杂度极高。
一致性(Consistency)的约束保障
一致性指事务执行前后,数据库必须处于业务定义的合法状态,比如账户总额不变、外键引用有效。它更像是原子性、隔离性、持久性共同作用的结果,而非单一机制。数据库通过约束、触发器与事务边界来维护。
例如我们定义check约束保证余额非负,若事务提交后破坏该规则,数据库直接拒绝提交。下面示例建表时声明约束:
CREATE TABLE account ( id INT PRIMARY KEY, balance DECIMAL(10,2) NOT NULL, CONSTRAINT chk_balance CHECK (balance >= 0) );
一致性不负责解决并发导致的临时中间状态,那是隔离性的工作。应用也应避免把业务规则完全寄托于数据库,双花问题常因应用层校验与事务边界错位而产生。
隔离性(Isolation)与并发控制
隔离性决定多个事务同时运行时彼此影响的程度。SQL标准定义四种隔离级别:读未提交、读已提交、可重复读、串行化。级别越低越可能出现脏读、不可重复读、幻读,但并发性能越好。
InnoDB在可重复读级别用MVCC提供一致性快照,写冲突靠行锁与间隙锁防止幻读。以下表格对比各级别问题:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 | 可能 | 可能 | 可能 |
| 读已提交 | 不可能 | 可能 | 可能 |
| 可重复读 | 不可能 | 不可能 | 基本避免 |
| 串行化 | 不可能 | 不可能 | 不可能 |
实际开发中,读已提交在Oracle中常用,而MySQL默认可重复读。选择时要权衡一致性与吞吐,盲目使用串行化会让系统几乎退化成单线程。
持久性(Durability)与redo日志
持久性保证一旦事务提交,结果永久有效,即使宕机也不丢失。实现依赖redo日志与写前日志协议:修改先写redo再返回提交成功,崩溃恢复时重放redo补齐数据页。
以下Java片段示意应用如何确认提交完成:
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false);
try {
// 执行SQL
conn.commit(); // 此时redo已落盘,断电也可恢复
} catch (Exception e) {
conn.rollback();
}
需要注意的是,若数据库使用异步刷盘且未开启双写,极端断电仍可能丢数据。因此核心系统常配合组提交与fsync策略平衡性能与安全。
总结与实践建议
理解ACID的实现原理有助于我们在设计交易、库存等系统时正确运用事务。高并发写场景应缩小事务粒度,长事务会拖挂undo与锁。排查数据异常时,可从隔离级别与日志落盘配置入手,而非只怀疑业务代码。
当业务跨越多个服务时,单机事务不再够用,可借助消息队列最终一致或分布式事务框架,但底层仍依赖各库的ACID能力。掌握这些原理,才能构建既快又稳的数据层。