知识卡片
防止丢失更新的四种手段原子操作显式锁自动检测与CAS
内容
“丢失更新”是一类特定的写写冲突:应用先读取一个值、本地修改、再写回(读取-修改-
写入序列),如果两个事务并发做这套动作,后写的会把先写的悄悄覆盖掉,覆盖掉的那次
修改就”丢失”了(典型场景:计数器自增、JSON文档局部修改、维基页面整页覆盖保存)。
防范手段有四种,各有取舍。原子写:如果能把操作表达成数据库自带的原子指令(如
UPDATE counters SET value = value + 1),就完全不需要应用代码自己做”读取-修改-
写入”,这通常是最好的方案,常见实现是在读取对象时加排它锁(游标稳定性)或强制
单线程执行;但ORM框架很容易在不知不觉中绕开这类原子操作、退化成不安全的读改写
序列。显式锁:如果数据库内置的原子操作不够用(比如多人游戏里移动棋子还要校验
游戏规则),应用可以自己用SELECT ... FOR UPDATE显式锁住要读改写的行,强制其他
并发事务排队等待。自动检测:让读改写序列并发执行,数据库在事务提交时检测是否
发生了丢失更新,一旦发现就中止并要求重试——这个方式的优点是不需要应用代码使用
任何特殊功能,PostgreSQL可重复读、Oracle可串行化、SQL Server快照隔离都支持自动
检测(但MySQL/InnoDB可重复读不支持,因此按某些学者的标准,MySQL其实没有真正
提供快照隔离)。比较并设置(CAS):只有当前值和上次读到的值一致才允许更新生效,
否则要求重读重试;但要小心,如果数据库的CAS判断依赖的WHERE条件是从旧快照读的,
CAS可能形同虚设——依赖这个操作前必须确认具体实现是否真的安全。
结构图:
flowchart TD
A[丢失更新: 并发读改写序列互相覆盖] --> B[原子写: 用数据库自带原子指令]
A --> C[显式锁: SELECT FOR UPDATE手动锁行]
A --> D[自动检测: 提交时检查并中止违规事务]
A --> E[CAS: 只在值未变时允许更新]
B -.ORM易意外绕开原子操作.-> B
D -.部分数据库如MySQL InnoDB不支持自动检测.-> D
E -.WHERE条件若读自旧快照可能形同虚设.-> E
参考来源
- 位置:《数据密集型应用系统设计》第七章《事务》"防止丢失更新""原子写""显式锁定"
"自动检测丢失的更新""比较并设置(CAS)"(源文件:_epub-src/ch7_split_003.html)
- 结论依据:原文分别介绍原子写操作、显式锁定、自动检测丢失更新、CAS四种手段的
实现方式和局限(ORM易绕开原子操作、部分数据库不支持自动检测、CAS可能因读自
旧快照而失效),直接支撑本卡片的结构梳理。
- 原始内容:许多数据库提供了原子更新操作……如果数据库的内置原子操作没有提供
必要的功能……让应用程序显式地锁定将要更新的对象……数据库可以结合快照隔离
高效地执行此检查……但是,如果数据库允许WHERE子句从旧快照中读取,则此语句
可能无法防止丢失更新。