跳转至

事务、并发与恢复

1. 银行转账与 ACID

账户 A 向账户 B 转账时,扣款和入账必须在同一事务中完成:

1
2
3
4
BEGIN;
UPDATE account SET balance = balance - 1000 WHERE account_id = 'A001';
UPDATE account SET balance = balance + 1000 WHERE account_id = 'A002';
COMMIT;

任何一步失败都执行 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 的具体实现依数据库产品而异,教材口径待核验。