第5章 并发

区分系统事务与业务事务,以隔离、不变量、锁和冲突恢复保护长会话。

学习目标

  • 能区分系统事务与跨请求的业务事务,并解释丢失更新、冲突检测和恢复成本
  • 能编写 TypeScript 乐观并发控制,覆盖版本匹配、冲突拒绝和可重试结果
  • 能根据冲突概率、损失大小、锁等待和回滚代价,判断何时采用乐观或悲观方案

为什么第5章并发值得单独学习

区分系统事务与业务事务,以隔离、不变量、锁和冲突恢复保护长会话。第5章并发的核心不是套用某个框架 API,而是回答:如何保护跨多个系统事务的业务事务,在冲突检测、提前锁定和恢复成本间作出选择。在两个客服并行修改订单中,如果无法说清责任、状态和失败由谁承担,即使正常请求能够返回,架构决定也没有完成。

不是把数据库事务时间拉长,而是明确用户会话、系统事务和冲突恢复的边界。本页以 2024 年中文版公开目录限定 第5章 并发 的范围,并依据 Martin Fowler 的作者图书页和模式目录独立重写。它不复现原书正文、插图或代码;目录只决定“要讲什么”,这里的案例、实验、判断题和答案均为本课程原创。

先建立直觉:问题、机制与代价

第5章并发面对的具体压力是“冲突概率、业务事务时长、锁粒度、等待时间与回滚代价”。它采用的机制可以概括为:用版本、锁和业务不变量检测或避免丢失更新。机制带来的收益必须与重试、死锁、用户重新编辑和锁管理成本同时记录,否则学习者只会得到一个没有拒绝条件的模式名称。

对第5章并发,优先比较低冲突场景的版本检查、高冲突场景的悲观锁和高损失场景的粗粒度保护。只有当 、冲突信号和恢复路径清晰时,才选择一种控制策略;如果只是把每次数据库提交成功误认为业务事务安全,就不能据此选择第5章并发。适合低冲突,悲观锁则要承担等待和死锁代价。

目录单元到教学证据

第5章并发

第5章并发 的学习边界里,第5章并发不是待背诵的目录词,而是用来检查业务事务是否把责任交给正确对象。对两个客服并行修改订单,学习者要记录执行语境、版本和恢复路径的可观察变化,并说明它何时支持或否定本章结论。

5.1 并发问题

并发问题首先表现为丢失更新、脏读和不一致决策。先固定两个参与者的读取时间和提交顺序,再定位第一个不变量被破坏的状态,才能判断需要检测、锁定还是改写业务流程。

5.2 执行语境

执行语境区分一次数据库系统事务和用户从打开到提交的业务事务。把请求、会话、事务和重试标在同一条时间线上,才能知道版本检查应该发生在哪里,以及重试是否会重复副作用。

5.3 隔离与不变性

隔离与不变性要求明确哪些读取可以并行、哪些状态必须原子改变。库存不能为负、订单不能重复支付等不变量应由领域规则和提交检查共同保护,而不是寄希望于页面顺序。

5.4 乐观并发控制和悲观并发控制

乐观并发控制在提交时检查版本,冲突后拒绝或重试;悲观并发控制在读取前取得锁,减少冲突却增加等待和死锁风险。选择证据应包括冲突概率、单次损失、用户恢复成本和锁可观测性。

5.5 事务

事务章节要区分数据库原子性、应用服务边界和跨系统补偿。一次提交成功不能证明整个业务事务成功;外部支付、消息发送和数据库写回应有明确的顺序、幂等策略和失败记录。

5.6 离线并发控制的模式

离线并发控制把用户编辑和最终提交分开,要求保存版本、差异和冲突提示。冲突不能静默覆盖,系统应让用户重读、合并或撤销,并保留可审计的恢复路径。

5.7 应用服务器并发

应用服务器并发需要说明请求是否共享内存、会话是否跨节点以及锁存放在哪里。无状态服务仍然需要数据库或分布式协调来保护不变量,不能把进程内互斥当成全局并发策略。

5.8 进一步阅读

进一步阅读应把本章证据卡连接到乐观离线锁、悲观离线锁、粗粒度锁和隐式锁等具体模式。下一步选择由冲突与恢复证据驱动,而不是由框架是否提供注解驱动。

专属设计案例:两个客服并行修改订单

把两个客服并行修改订单切成“业务事务 → 读取版本 → 并发修改 → 冲突检测 → 提交恢复”五个观察点。第5章并发的设计草案必须写出谁拥有状态、谁作出业务决定、失败怎样传播,以及执行语境、隔离、不变量、乐观悲观、事务中哪个指标最先提示当前方案不再适用。

设计记录采用五个可换行字段:单元键为 poeaa24-chapter-05-concurrency;模式族为 concurrency;裁决是“能解释并发的边界与选择轴,逐项覆盖 8 个目录节点,并在同一应用切片中验证”;观测项包括执行语境、隔离、不变量、乐观悲观、事务;拒绝条件是“没有冲突信号、恢复路径或跨请求事务所有者”。

配置不是生产框架语法,而是一张评审卡。对第5章并发的任何实现都要能把运行证据重新映射到这张卡;如果更换数据库、应用服务器或部署平台后无法回答同一组问题,说明决定依赖的是工具偶然行为而不是并发语义。

并发冲突解剖

两个客服读取同一版本,各自修改不同字段,后保存者的完整快照可能覆盖先保存者的工作。

乐观 vs 悲观:离线并发控制的两条路径乐观离线锁Optimistic Offline Lock1. 读取记录(版本 v1)不加锁,自由读取2. 本地编辑(多步操作)用户修改字段3. 提交:检查版本 == v1 ?SELECT version WHERE id=?4a. 匹配 → 写入(v1→v2)UPDATE ... SET version=v24b. 不匹配 → 拒绝 + 提示冲突!请刷新后重试悲观离线锁Pessimistic Offline Lock1. 获取排他锁SELECT ... FOR UPDATE2. 读取记录其他事务被阻塞3. 本地编辑(多步操作)锁一直持有中...4. 提交写入UPDATE ... 无需版本检查5. 释放锁COMMIT / ROLLBACK锁持有期间VS代价:冲突时回滚重试适用:低冲突、短事务、读多写少代价:锁等待 + 死锁风险适用:高冲突、高损失、长事务选择轴:冲突概率 × 单次冲突损失 → 低冲突优先乐观,高冲突或高损失再考虑悲观
乐观离线锁在提交时检测版本冲突,代价是冲突时重试;悲观离线锁在读取前获取排他锁, 代价是锁等待和死锁。选择取决于冲突概率与单次冲突造成的业务损失。

图中对比提交时版本检查与读取时持有排他锁,帮助学习者把冲突成本、等待成本和恢复动作放到同一条时间线上。

并发策略:冲突概率 × 损失 × 恢复成本低冲突乐观版本检查拒绝后重读或合并高冲突短操作悲观锁或原子扣减控制等待与死锁高损失跨服务串行化、幂等与补偿不能靠最后写入者胜出拒绝条件:没有冲突信号、恢复路径或不变量测试策略由冲突与恢复证据推动,而不是由数据库锁功能推动
并发策略应结合冲突概率、单次损失与恢复成本选择,避免把长业务事务直接变成长数据库锁。

第二张图把冲突概率、单次损失和恢复成本转成策略选择顺序,避免把锁或重试当成无条件的默认答案。

选择与拒绝矩阵

评审问题选择第5章并发策略的证据应拒绝或改用其他方案的信号
责任业务事务到提交恢复的所有者清晰页面或数据库默认顺序承担业务规则
变化冲突处理能局部替换并保持不变量每次新增字段都要重写所有锁逻辑
失败版本冲突、死锁、超时和重试可分别观测冲突被静默覆盖成最后写入者胜出
替代已比较乐观、悲观和粗粒度保护的恢复成本因数据库支持行锁就直接锁住长会话

代码实践:版本检查保护订单更新

下面的存储接口用 expectedVersion 拒绝过期写入;调用者可以把冲突显示给客服,而不是静默覆盖。必须和用户恢复动作一起设计。

type Order = {
  id: string;
  version: number;
  note: string;
  status: "open" | "paid";
};
 
interface OrderStore {
  read(id: string): Order | undefined;
  update(order: Order, expectedVersion: number): "updated" | "conflict";
}
 
function saveNote(
  store: OrderStore,
  id: string,
  expectedVersion: number,
  note: string,
): "saved" | "conflict" | "not-found" {
  const current = store.read(id);
  if (!current) return "not-found";
  const result = store.update(
    { ...current, note, version: expectedVersion + 1 },
    expectedVersion,
  );
  return result === "updated" ? "saved" : "conflict";
}
 
function retryAfterConflict(
  store: OrderStore,
  id: string,
  note: string,
): string {
  const latest = store.read(id);
  if (!latest) return "not-found";
  const result = saveNote(store, id, latest.version, note);
  return result === "saved" ? "saved-after-reread" : result;
}

这段代码把过期写入变成显式结果,调用者可以展示冲突、让客服重读后再提交。真实系统还要防止重试重复发送支付或消息副作用;对于高冲突高损失场景,应另外评估悲观锁、串行队列或粗粒度锁。

常见误区

可验证练习

练习

本组练习覆盖 5.1 并发问题、5.2 执行语境、5.3 隔离与不变性、5.4 乐观并发控制和悲观并发控制、5.5 事务、5.6 离线并发控制的模式、5.7 应用服务器并发和 5.8 进一步阅读。

问题 1:识别冲突。 两个客服读取版本 4,分别修改备注和状态,第二次提交时应该接受吗?

问题 2:选择策略。 库存扣减冲突概率很高且损失直接影响结算,应优先考虑什么?

问题 3:检查应用服务器。 在多节点无状态服务器中使用进程内锁,能保护所有订单吗?

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

并发

多个参与者在同一业务状态上同时读取、修改和提交时,需要保持不变量并处理冲突的系统行为。

不变量

无论并发顺序如何都必须保持的业务约束,例如库存不能为负。

乐观并发控制

把业务事务期间读取到的版本与提交时的当前版本比较,以拒绝过期写入的策略。

冲突拒绝

提交时发现读取版本已经变化后返回的可恢复结果,要求调用者重读、合并或撤销。

本章小结

掌握第5章并发的标志不是记住定义,而是能在两个客服并行修改订单中解释“业务事务 → 读取版本 → 并发修改 → 冲突检测 → 提交恢复”的责任链,利用执行语境、隔离、不变量、乐观悲观、事务作出可证伪的选择,并在缺少冲突信号或恢复路径时明确拒绝当前实现。

前后导航

来源与改写范围

资料与写作方式声明

本章以Martin Fowler《企业应用架构模式》与公开模式目录权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…