10 顾此失彼:并发控制与隔离异常
第 10 章 顾此失彼:并发控制与隔离异常
单条 SQL 正确、单个事务顺序执行正确,不代表并发执行仍正确。并发控制的起点不是先选隔离级别,而是写出业务不变量和允许的失败语义:
business invariant
→ concurrent read/write set and possible interleavings
→ snapshot/isolation guarantee
→ atomic SQL, row lock, optimistic CAS, SSI or advisory coordination
→ retryable SQLSTATE + whole-transaction replay
→ idempotency and external-effect protocol
→ lock/error/latency evidence
→ two-connection invariant testPostgreSQL 的 Read Committed、Repeatable Read 与 Serializable 不是“性能低、中、高”的旋钮。它们允许或拒绝的交错不同;拒绝通常表现为需要应用处理的 40001,不是数据库自动把原事务重新执行。
本章目标
完成本章后,读者应当能够:
- 区分 Read Committed 的语句快照与 Repeatable Read 的事务快照;
- 知道 PostgreSQL 的 Read Uncommitted 实际等同 Read Committed;
- 解释 PostgreSQL Repeatable Read 是 snapshot isolation,仍允许 write skew;
- 说明 Serializable Snapshot Isolation 如何用
SIReadLock识别危险依赖; - 把
40001理解为“整个事务必须重放”,而非重试最后一条 SQL; - 用确定性交错重现 read–compute–write lost update;
- 用
SET col = col - $1 WHERE ...做原子条件更新; - 用 version compare-and-swap 识别并处理乐观冲突;
- 区分
FOR UPDATE、FOR NO KEY UPDATE、FOR SHARE与FOR KEY SHARE; - 正确选择
NOWAIT、SKIP LOCKED,并说明后者为何只适合 queue-like workload; - 建立统一 lock order,识别
40P01并回放整个事务; - 区分 session-level 与 transaction-level advisory lock 生命周期;
- 设计 advisory key namespace、owner、timeout 与释放协议;
- 使用
pg_stat_activity、pg_locks和pg_blocking_pids()保存精确阻塞图; - 用 Pigsty 的 Activity/Xacts/PGCAT Locks/日志时间窗量化并发问题;
- 设计 payment idempotency key、payload fingerprint 与响应复用;
- 用 transactional outbox 跨越数据库 commit 与外部消息边界;
- 把隔离、重试、幂等与外部副作用要求追加到
DEFAULT-TXNN-007。
实验边界
实验基线为 PostgreSQL 18.4、Pigsty v4.4.0、Ubuntu 24.04 L1;主体机制保持 PostgreSQL 14–18 可用。只创建六张带固定 marker 的表:
ch10_inventory two SKUs / available=100 / version=0
ch10_doctor two on-call doctors
ch10_deadlock_probe two lock-order rows
ch10_job six queued jobs
ch10_payment_request idempotency authority
ch10_outbox committed external-effect intent协调器使用 advisory 两整数 key space:
(3610, 1001..1016)它只控制教学 interleaving,不参与业务正确性。每个 case 后必须 worker=0、barrier lock=0。setup 在 marker 匹配后重建,属于 R1;reset 属于 R2,需要 action/target 双 token。
下载资产:
- 实验合同
- 上下文 guard
- 确定性 fixture
- 并发协调与断言器
- lost-update worker
- 原子更新 worker
- 乐观更新 worker
- 乐观重试
- Repeatable Read worker
- write-skew/SSI worker
- 行锁 holder
- 行锁 waiter
NOWAITprobeSKIP LOCKEDworker- deadlock worker
- payment idempotency worker
- 不同 payload 反例
- 最终状态验收
- 双 token reset
- 语义审查器
- v0.5 规则提案
- 任务入口
所属位置
- 卷别:上卷:应用开发(独立导读页,不构成章节父目录)
- 教学分组:第二篇:应用——从 SQL 正确走向稳定交付
- 兼容入口:
/ch10/、/volume-1/concurrency-isolation/
本章目录
10.1 隔离级别与可观察现象
10.2 Lost update 不是一句口号
10.3 悲观锁与锁队列
10.4 乐观控制、重试与幂等
10.5 咨询锁与跨行协调
10.6 观察与诊断并发
10.7 实战:库存扣减与支付幂等
实测摘要
一次 PostgreSQL 18.4 全量验收得到:
lost:
both read 100 / requested total 30
serial expected 70 / actual 80 or 90
safe writes:
atomic → 2 successes / final 70 / version 2
optimistic → first 1 success + 1 conflict
whole retry 1 success / final 70 / version 2
isolation:
Repeatable Read same-row update → 1 commit + one 40001
Repeatable Read write skew → 2 commits / on-call 0
Serializable write skew → SIReadLock observed
1 commit + one 40001 / on-call 1
locks:
NOWAIT=55P03
deadlock=one 40P01 / survivor leaves rows [1,1]
SKIP LOCKED=two workers × 3 / duplicate 0
row lock=one blocker edge / waiter sees 90 / final 70
idempotency:
concurrent requests 2 / inserted 1 / reused 1
payment 1 / outbox 1 / distinct response 1
same key different payload=P0001 / state unchanged
final:
worker=0 / advisory barrier=0
relation checksum=f8a7bfae59c6d16cd323abecfefe1014胜出事务、PID、XID、backend_start、lost-update 最终是 80 还是 90 都不是 golden。稳定断言是允许/拒绝的交错、SQLSTATE、多连接关系、业务不变量和最终清理。
章节验收
- 每个并发写先声明 invariant、read set、write set 与 failure contract;
- Read Committed 的两条普通
SELECT可见不同已提交状态; UPDATE SET x=x+...与 application read–compute–write 的语义差别明确;- optimistic zero-row update 被当冲突,而非成功;
- Repeatable Read concurrent row update 以 40001 拒绝;
- Repeatable Read write skew 反例被实际重现;
- Serializable 的
SIReadLock和 40001 都有 raw evidence; - 整事务 retry 有 attempt/time budget、backoff/jitter 与新 snapshot;
- row lock mode 与 foreign-key key update 边界正确;
NOWAIT55P03、SKIP LOCKEDqueue-only 边界明确;- 所有多行写有统一 lock order,40P01 仍被整事务处理;
- advisory key namespace/lifetime/owner/timeout 可审计;
- 阻塞动作使用 PID + backend_start + database + application identity;
- Pigsty 时间窗能落回 exact blocker graph、SQLSTATE 与 transaction age;
- idempotency key 绑定 payload fingerprint 与 response;
- 相同 key 不同 payload 必须拒绝;
- 数据库事务内只写 outbox,不调用远程支付/消息;
task.sh all、双 token reset 与错误 token 反例均通过;- 最终 worker/advisory=0,业务 checksum 不变;
- v0.5 仍是有依赖的 candidate,不冒充已发布 baseline。
下一章 ch11《守正出奇:模式变更与安全发布》 将把本章的锁、事务与兼容语义应用到真实 DDL 发布。
参考资料
- PostgreSQL 18:Transaction Isolation
- PostgreSQL 18:Explicit Locking
- PostgreSQL 18:Serialization Failure Handling
- PostgreSQL 18:Advisory Lock Functions
- Pigsty:PostgreSQL Dashboards
上一章:巧夺天工:索引设计与效果验证 · 返回上卷导读 · 下一章:守正出奇:模式变更与安全发布 · 查看全书目录 · 查看索引中心
最后更新于