简介
当前很多数据库的 ACID 特性会基于 MVCC Snapshot Isolation 来实现,这样读写不会相互阻塞,同时能够提供原子能力。另外,关于 MySQL 的可以参考 InnoDB 隔离级别 中的介绍。
ACID
是 Atomic
原子性、Consistency
一致性、Isolation
隔离性、Durability
持久化的简写,如下简单整理相关基本概念。
Atomic: 要么全部生效 (Commit),要么全部撤销 (Rollback),不会出现中间态
> 日志管理
Redo 相关修改操作需要在提交前落盘
Undo 用于故障恢复以及事务回退
Consistency: 并发执行过程中,确保数据的合法性,满足完整性约束
> 业务层面保证,转账不会出现已经扣减但是未收到情况
> 各种约束,例如唯一性、外键、触发器等
Isolation: 确保并发事务是可串行化的,可见性和性能的均衡
> 并发控制,消除可能带来的异常,脏读、不可重复读、幻读等
通常会采用两阶段锁、MVCC/Snapshot、时间戳、乐观并发控制
> 隔离级别
未提交读 (Read Uncommitted)
已提交读 (Read Committed)
可重复读 (Repeatable Read)
可串行化 (Serializability)
Snapshot (Snapshot Isolation)
Durability: 保证已经提交数据不会丢失
> 缓冲区脏页刷新,可以是异步,同时考虑污染
> Checkpoint 机制,防止日志无限膨胀
> Redo/Undo 用于故障恢复
> 复制,可以是逻辑复制、物理复制,不同场景要求不同
MVCC
自从发明了 Multi-Version Concurrency Control, MVCC
之后,几乎所有的数据库都开始抛弃 (部分抛弃) 性能较差的两阶段锁的并发控制,所以,后续兴起分布式系统几乎都采用 MVCC 的并发控制方案。
异常
其中左侧的是 A 事务,而右侧是 B 事务。
Dirty Write
未提交的事务 A 不受并发 B 事务写的影响。
a> BEGIN;
a> INSERT INTO genres VALUES (5, 'Western');
b> INSERT INTO genres VALUES (5, 'Romance');
Error: Serialization failure, retry transaction
a> SELECT * FROM genres WHERE id = 5;
5|Western
当写冲突时会存在一个事务报错,可以根据回退成本选择,或者简单点,回退后面的事务。
Dirty Read
未提交的事务 A 在事务 B 中不可见,提交之后对事务 B 才可见。
a> BEGIN;
a> INSERT INTO genres VALUES (5, 'Western');
b> SELECT * FROM genres WHERE id = 5;
No rows returned
a> COMMIT;
b> SELECT * FROM genres WHERE id = 5;
5|Western
Lost Update
两个事务 A B 同时读取一个值,当依次更新时,首先写入的不应该被后面的写入覆盖。
a> BEGIN; b> BEGIN;
a> SELECT title, rating FROM movies WHERE id = 2; b> SELECT title, rating FROM movies WHERE id = 2;
Sicario|7.6 Sicario|7.6
a> UPDATE movies SET rating = 7.8 WHERE id = 2;
b> UPDATE movies SET rating = 7.7 WHERE id = 2;
Error: Serialization failure, retry transaction
a> COMMIT;
此时合理的处理逻辑是,重启一个事务进行读取+更新操作。
Fuzzy Read
事务 B 不应该立即看到某个事务的修改,即使该事务已经提交。
a> BEGIN; b> BEGIN;
b> SELECT * FROM genres WHERE id = 1;
1|Science Fiction
a> UPDATE genres SET name = 'Scifi' WHERE id = 1;
a> COMMIT;
b> SELECT * FROM genres WHERE id = 1;
1|Science Fiction
b> COMMIT;
b> SELECT * FROM genres WHERE id = 1;
1|Scifi
Read Skew
当事务 A 读取两个值时,如果 B 在读取中间修改了某个值,那么 A 读取到的应该是老的值。
a> BEGIN;
a> SELECT * FROM genres WHERE id = 2;
2|Action
b> BEGIN;
b> UPDATE genres SET name = 'Drama' WHERE id = 2;
b> UPDATE genres SET name = 'Action' WHERE id = 3;
b> COMMIT;
a> SELECT * FROM genres WHERE id = 3;
3|Drama
Phantom Read
当事务 A 查询某个范围时,而此时 B 提交满足 A 查询条件的数据,那么 A 不应该查询到 B 写入的数据。
a> BEGIN;
a> SELECT * FROM genres WHERE id > 2;
3|Drama
4|Comedy
b> INSERT INTO genres VALUES (5, 'Western');
a> SELECT * FROM genres WHERE id > 2;
3|Drama
4|Comedy
Write Skew
Write skew: when A reads row X and writes it to row Y, B should not concurrently be able to read row Y and write it to row X.
a> BEGIN; b> BEGIN;
a> SELECT * FROM genres WHERE id = 2;
2|Action
b> SELECT * FROM genres WHERE id = 3;
3|Drama
b> UPDATE genres SET name = 'Drama' WHERE id = 2;
a> UPDATE genres SET name = 'Action' WHERE id = 3;
a> COMMIT; b> COMMIT;
Here, the writes actually go through. This anomaly is not protected against by snapshot isolation, and thus not by toyDB either - doing so would require implementing serializable snapshot isolation. However, this is the only common serialization anomaly not handled by toyDB, and is not among the most severe.
参考
- Berenson H, Bernstein P, Gray J, et al. A Critique of ANSI SQL Isolation Levels ACM SIGMOD Record. ACM, 1995, 24(2): 1-10.