事务、并发与恢复¶
1. 银行转账与 ACID¶
账户 A 向账户 B 转账时,扣款和入账必须在同一事务中完成:
任何一步失败都执行 ROLLBACK,不能保留“只扣款未入账”的中间结果。
- 原子性:全部成功或全部失败。
- 一致性:事务前后从一个合法状态到另一个合法状态。
- 隔离性:并发事务不能看到不该看到的中间状态。
- 持久性:提交后的修改在重启后仍保存。
2. 隔离级别与并发异常¶
两个转账请求同时读取余额并扣款,可能产生超额转出。常见隔离级别如下:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| Read Uncommitted | 可能 | 可能 | 可能 |
| Read Committed | 避免 | 可能 | 可能 |
| Repeatable Read | 避免 | 避免 | 一般仍可能 |
| Serializable | 避免 | 避免 | 避免 |
脏读是读到另一事务尚未提交且最终回滚的数据;不可重复读是同一事务两次读同一行结果不同;幻读是两次范围查询的行集合因新增或修改而不同。隔离越强,一致性保护越强,但并发性能通常越受影响。不同数据库的默认级别和对幻读的具体实现待核验。
3. 锁与死锁¶
共享锁 S 允许多个事务同时读;排他锁 X 用于修改并阻止冲突访问。转账扣款常先锁住账户记录,再检查余额和修改。两阶段锁协议要求事务先加锁,释放锁后不能再加锁。
死锁例:事务 1 持有 A 等待 B,事务 2 持有 B 等待 A。四个必要条件是互斥、请求并保持、不可剥夺、循环等待。可以通过破坏必要条件预防,或采用超时、检测与恢复等策略;具体数据库的锁粒度和死锁选择规则待核验。
4. 日志、WAL 与检查点¶
Redo Log 记录修改后的信息,用于重做已提交事务,主要保障持久性;Undo Log 记录修改前的值或反向操作,用于回滚未提交事务和读取旧版本,主要保障原子性。WAL(Write-Ahead Logging)要求修改数据页前先将对应日志写入并落盘。检查点定期刷盘并推进日志位置,缩短故障恢复时间。
若转账在扣款后、入账前断电,重启时数据库依据日志对已提交事务 redo、未提交事务 undo,避免留下半成品。SELECT 不写入用于故障恢复的事务日志;审计日志可记录谁在何时执行了什么 SQL(包括 SELECT),通用查询日志用于调试,慢查询日志用于性能分析,三者用途不同。
日志名称、持久化时机和 MVCC 的具体实现依数据库产品而异,教材口径待核验。