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 反复投诉。
关联
- 前置:25.1 乐观并发与悲观并发
- 并列:25.2 MVCC、25.6 CRDT (计数 / 集合 / 寄存器)
- 下游:23.1 物理时钟与漂移、67.6 秒杀与抢购 (库存 / 限购 / 防刷 / 削峰)