知识卡片

悲观锁用读写锁、乐观锁用版本标记,应对不一致读

普通读书笔记卡

内容

不一致读容易被忽略,因为大多数人只把更新丢失当成并发的本质问题——但David在Martin不知情的情况下修改了Order类接口、Martin基于旧接口编译提交,同样会破坏共享代码。两种并发控制策略各有一套应对不一致读的机制。悲观锁:靠读锁(共享锁,允许多人同时持有)和写锁(排他锁,独占)配合——只要有人持有读锁,别人就拿不到写锁;只要有人持有写锁,别人两种锁都拿不到,用这套规则就能杜绝不一致读。乐观锁:通常靠给数据打版本标记(时间戳或顺序计数器)来检测冲突——提交更新前核对本地版本标记和共享数据当前版本标记是否一致,不一致就说明有冲突;检测不一致读的思路类似,所有读取过的数据都要跟共享数据的版本标记比对,任何差异都意味着冲突已发生。但如果对所有读取过的数据都做这种访问控制,很容易把并不严重的冲突也当成问题处理,因此值得区分”被用来做决策的数据”和”仅仅顺带读到的数据”——产品列表里新增一条产品通常无关紧要,但已经汇总完的费用清单被改动就严重得多;这种区分没有捷径,需要针对具体数据仔细分析它的用途。另一种思路是时序读:每次读取都附带一个时间戳或标签作为约束,让数据库按这个时间点返回数据——数据库很少原生支持这个能力(因为要求提供完整的修改历史时序列表,代价高昂),但源代码控制系统里很常见;如果某个领域确实需要这种能力,可以参考Snodgrass与Fowler TP两份文献了解具体实现方法。

参考来源

- 位置:《企业应用架构模式》第一部分"表述"之"第5章 并发"之"5.4.1 避免不一致读"(源文件:_epub-src/OEBPS/Text/000033.html) - 结论依据:原文说明"悲观锁策略可以通过读加锁和写加锁来处理这个问题。读数据时需要一个读锁……写数据的时候,需要一个写锁……乐观锁策略通常将冲突检测建立在数据的某种版本标记上……为了检测到更新丢失,系统核对将要更新数据的版本标记和共享数据的版本标记……检测不一致读本质上是类似的:所有读取的数据都需要跟共享数据进行版本标记比较",直接支撑本卡结论。 - 原始内容:另一种处理不一致读问题的方法是使用时序读(Temporal read)。在每次读取数据的时候都使用某种时间戳或其他不变的标签作为约束条件,数据库根据时间或者标签返回数据。