知识全景图/ 软件工程与系统/ 数据库生产治理/ 如何设计数据库锁、隔离级别与死锁治理

如何设计数据库锁、隔离级别与死锁治理

不要先背脏读幻读;先解释“两个窗口同时改预算,为什么都成功却覆盖了”

这一页只解决一个问题: 并发写入下,业务不变量怎样不被写坏,同时不把系统锁死。锁和隔离级别不是面试概念,而是并发代价管理。
一、事故:两个窗口同时改预算,后提交覆盖先提交

窗口 A 把餐饮预算从 3000 改到 3500;窗口 B 仍基于旧页面改成 3200。两个请求都返回成功,最后 A 的修改被静默覆盖。

错误反应为什么不够逼出的设计
给接口加事务两个事务都能成功提交需要冲突检测
提高隔离级别不一定阻止最后写覆盖,还可能增加等待先判断害怕哪种异常
最后写入为准用户不知道自己被覆盖version 乐观锁和冲突提示
二、选机制前只问 6 个问题
问题常见机制
怕最后写覆盖吗version 字段、乐观锁、更新行数校验
怕重复扣减吗条件更新、行锁、唯一约束
怕范围内重复创建吗UNIQUE(tenant_id, month),不要只先查再插
冲突高还是低低冲突用乐观锁,高冲突锁明确业务对象
事务里有没有外部副作用支付、MQ、HTTP 不能跟数据库锁绑在一起等
死锁后能不能重试只有幂等且无外部副作用的动作才能有限重试
三、最小并发模型
budgets - id - tenant_id - month - category_id - amount - version optimistic update: UPDATE budgets SET amount = ?, version = version + 1 WHERE id = ? AND tenant_id = ? AND version = ?;
吸收点: 乐观锁不是“不加锁”,而是提交时证明自己修改的是看到的那一版。更新 0 行就是业务冲突,不是成功。
四、锁最容易翻车的 4 个边界
边界事故设计动作
锁对象锁太大拖慢所有人,锁太小挡不住不变量锁住预算行、账户行、结账状态行这类明确对象
加锁顺序A 先锁转出,B 先锁转入,互相等待固定顺序,例如账户 id 从小到大
事务长度事务里做报表、HTTP、MQ,别人一直等事务内只做最短校验和写入
重试边界死锁重试导致重复支付或重复消息幂等键、Outbox、有限退避重试
关键边界: 数据库锁期间不要等待外部系统。HTTP、MQ、文件、人工审批都不该放进持锁事务。
五、用 6 个样本证明并发可信
样本通过标准
两个窗口基于同一 version 改预算一个成功,一个冲突提示
并发扣减余额不会扣成负数,更新行数可解释
并发创建同月结账唯一约束保证只成功一次
两个事务反向锁账户能检测死锁,重试有上限
月报长事务扫热库事务年龄告警,必要时拆批或走副本
死锁后重试带外部消息消息不会重复发,或通过 Outbox 隔离
六、下一步只按并发问题下钻
如果问题是业务接口幂等
如果问题是生产锁事故
如果想理解内部事务机制
事务与 WALMVCC
如果要做并发测试门禁
总结: 锁和隔离级别不是背概念。先说清业务怕哪种并发异常,再选择乐观锁、悲观锁、唯一约束、条件更新、固定加锁顺序和有限重试。能把这几件事说清,才算真的会设计并发数据库写入。