设计数据密集型应用-第七章-事物处理(Transaction)的问题和方案
几乎所有的数据库(关联型,非关联型)都涉及到事物处理,而保证事物处理的安全可靠,依赖于四个准则ACID(Atomicity, Consistency, Isolation, Durality);
- 原子性(Atomicity), 即一个操作不可以在被细分到更细的操作;用来保证操作的完成型,即要么成功写入/更新或者失败回复到原状;实际实现方案,日志
- 一致性 (Consistency), 即数据的一致性,在进行任何数据操作后,数据始终保持连贯;
- 独立性(Isolation), 每个对数据的事物操作互相独立, 实际实现方案用锁;
- 可持久性(Durality), 即每个成功写入的事物操作,不会被系统所丢弃,即使系统崩了;
在实际的数据库实现中,如果遵循严格的独立性(Isolation), 会导致数据库性能收损, 所以在实际使用中普遍通过弱独立性来确保, 大多数都确保一下两点:
- 当你读数据库时,你只会看到已经提交完成的数据(No Dirty Read),实际通过读取多个数据版本, 返回最新版本;
- 当你写数据库时,你只会覆盖已经提交完成的数据(No Dirty Write), 实际通过锁来实现
然而在实际的数据库中,即使保证了上面两点,也可能到至读时,数据的歪曲(read skew), 借助书中的图来解释,
问题, Alice有两个账户分别有500刀, 操作员将账户2中的钱转到转户1,在这个过程中,Alice有几率看到账户1 里的钱是500,而账户2里的是400(500 + 400 != 1000), 出现读偏差;
为了解决上述问题,提出来通过镜像隔离的方式防止数据读偏差(read skew), 具体的实施方法是通过多版本的同步控制(Multi-version Concurrency Control, MVCC), 核心想法:写操作重来不阻碍读,读操作不阻碍写;
独立性的保证还有另一层,即防止更新丢失(Preventing Lost Update), 在实际应用场景中,主要为了防止同时写入的json字段丢失(read-modify-write),或者自增字段丢失, 一些具体的实际方案:
- 原子写操作,通过锁实现(exclusive lock)
- 实际更新前,通过SQL锁进行操作
- 自动检测更新丢失,通过循序执行实现
- 比较-更新,在更新前比较数据是否变动,再进行更新;
以上的方案,在实际其他场景中会导致其他问题,比如书中提到的写偏差(即系统层面需要满足一定条件,但多个写入操作同时更新时,由于同步问题导致数据写入变差,导致最后系统条件无法满足), 即基于查询结果的更新(查询结果可能会变);
以上的独立性方案,在实际实现中,并不是所有数据库都保持一致的,书中最后介绍了另一种独立性方案-序列化独立方案 (Serialize Isolation),
- 序列化执行方案 (Actual Serial Execution)
- 双状态锁(Two-Phase Locking - 2PL), 读和写分别加锁;
- 序列化镜像独立方案(Serializable Snapshot Isolation, SSI), 以上面两种相比(悲观想法,防止一切可能发生的冲突和问题),这种方式是一种乐观方案, 所有事物保持进行,知道执行前(commit), 如果检测到状态异常,则放弃该操作, 简单的两个例子:
- 在执行操作时检测当前数据版本数据和已知版本是否一致或改动;
- 检测已有的写操作是否影响原有的读操作
结束语
总得来说这章可读性较强,内容较多,但涉及到较多落实细节,需要去看原有材料;