25.3 冲突检测与解决策略

一句话

冲突处理分两步:检测(怎么知道两个写打上了架)与解决(谁赢、怎么合);而第一步之前还有个更关键的问题——这个冲突在业务上意味着什么,答案不同,策略完全不同。

检测手段

手段 原理 适用
版本号/时间戳 提交时比对基线 单键、单行(见 25.1 乐观并发与悲观并发)
版本向量 记录 “见过哪些副本的哪些版本” 多主/离线编辑,能识别并发写
约束冲突 唯一键、外键、检查约束 由数据库直接拒绝
因果依赖 依赖的写尚未可见 需要因果一致性支持

解决策略

策略 语义 风险
LWW(最后写入胜) 按时间戳覆盖 静默丢更新;时钟漂移会让 “更早” 的写赢
合并(merge) 字段级/业务级合并 需要业务语义(如购物车取并集)
CRDT 数学上可交换合并 只能表达特定结构(见 25.6 CRDT (计数 / 集合 / 寄存器))
拒绝 + 重试 冲突即失败,客户端重来 高冲突下重试风暴
人工仲裁 提示用户选择 体验差,但正确性最高

选择的判据

业务语义 策略
覆盖型(头像、昵称) LWW 可接受
累加型(计数、余额) 需要原子操作或 CRDT 计数器,不能 LWW
集合型(标签、购物车) 取并集(OR-Set)
互斥型(库存、座位) 拒绝 + 重试(乐观)或锁(悲观)
金额/账务 拒绝 + 人工/对账,绝不 LWW

代码:三种冲突策略的落地形态

-- ① 拒绝 + 重试(库存、座位):CAS 语义
UPDATE stock SET n = n - 1 WHERE sku = ? AND n >= 1;   -- 影响 0 行 = 冲突

-- ② 合并(购物车/标签):取并集,天然幂等
INSERT INTO cart_items(user_id, sku) VALUES (?, ?)
ON CONFLICT (user_id, sku) DO NOTHING;

-- ③ LWW(仅限可覆盖字段):需要版本号,不能用墙钟
UPDATE profile SET nickname = ?, ver = ? WHERE id = ? AND ver < ?;

反模式

反模式 后果
一律用 LWW 丢更新(尤其金额、库存)
用客户端时间戳做 LWW 时钟不可信,旧写覆盖新写(见 23.1 物理时钟与漂移)
冲突只在 “写” 时检测 读到中间态,业务判断错
不记录冲突 无法复盘,也不知道丢了多少

| 冲突解决策略不告诉用户 | 数据被覆盖却无人知晓 | | 只在客户端解决冲突 | 服务端仍可能互相覆盖 |

三条铁律

🔑 先问业务语义,再选策略;没有通用解。

🔑 金额与库存禁止 LWW;它们需要 “拒绝 + 对账”。

🔑 冲突要可观测:计数、采样、告警,否则丢更新无人知道。

自检

Q1 为什么金额绝不能用 LWW?

▸ 答案:因为 LWW 会静默丢弃一次更新:两笔并发扣款只保留时间戳更晚的那笔,另一笔消失,且账面上看不出来。金额必须走 “原子扣减 + 幂等 + 对账”,冲突时拒绝而不是覆盖。

Q2 版本向量相比单一版本号,多解决了什么?

▸ 答案:它能判断两个写是 “并发” 还是 “有因果先后”:单一版本号只能比大小,无法区分 “B 在 A 之后发生” 与 “B 与 A 并发”。多主/离线场景必须用版本向量才能正确合并。

Q3 库存扣减该用哪种策略?

▸ 答案:拒绝 + 重试(乐观)或行锁(悲观),核心是 “不允许超卖”。常见做法是 UPDATE stock SET n=n-1 WHERE sku=? AND n>=1,影响行数 0 即失败;高并发下配合队列削峰(见 67.6 秒杀与抢购 (库存 / 限购 / 防刷 / 削峰))。

Q4 合并策略的难点在哪?

▸ 答案:在业务语义:字段能不能相加?列表取并集还是取最新?删除与新增如何交互?技术只能提供 “可合并的数据结构”(CRDT/OT),但 “怎么合才符合业务” 必须由领域专家定义,否则合并结果在业务上仍是错的。

Q5 冲突检测的成本从哪来?

▸ 答案:两处:存储(版本号/版本向量要随行保存)与计算(每次提交要比较、每次合并要遍历)。版本向量随副本数增长,大集群下元数据可能比数据还大——所以要按 “冲突概率” 决定是否值得精确检测。

Q7 为什么 “② 合并” 在跨设备场景比 “① 拒绝” 体验更好,但在库存场景是灾难?

▸ 答案:因为合并的语义是 “两个写都保留”:购物车加同一件商品取并集很自然;库存扣减若 “两次都保留”,结果是两次扣减都生效——对买家是好事,对卖家是超卖。冲突策略必须匹配业务语义,不能用一套逻辑套所有场景。

Q6 冲突检测的 “成本” 具体花在哪?

▸ 答案:三处:存储(版本号/版本向量随每行保存)、计算(每次提交比较、每次合并遍历)、协调(多副本时要交换版本信息)。版本向量随副本数线性增长,大集群里元数据可能超过数据本身——所以要先评估 “冲突概率”,再决定是否值得精确检测。

Q7 为什么 “冲突解决策略” 必须写进产品文档?

▸ 答案:因为它决定了用户看到什么:LWW 会让 “后写的覆盖先写的”(用户可能丢掉刚输入的内容),合并会让 “两边都保留”(用户看到重复项),拒绝会让 “操作失败”(用户需要重试)。这些都不是纯技术决策,必须让产品与用户知情,否则会被当成 bug 反复投诉。

关联