知识卡片

防止丢失更新的四种手段原子操作显式锁自动检测与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子句从旧快照中读取,则此语句 可能无法防止丢失更新。