想象一下,你是一家繁忙餐厅的后厨主厨,多位厨师同时开火做菜,大家共用刀具、砧板、炒锅和烤箱。厨师A一手握着菜刀、等着用炒锅;厨师B一手端着炒锅、急着要菜刀。两个人谁也不肯先放下手里的工具,于是整个厨房僵在原地,谁也做不出菜——这就是数据库世界里著名的“死锁”。 好在,聪明的后厨管理员可以提前定规矩,让这种僵局从一开始就不可能出现。比如:给所有厨具编上号,要求必须按从小到大的顺序去拿;或者按进厨房的先后定资历,年轻厨师见到前辈占着工具要让路,资深厨师则可以理直气壮地请年轻人挪地方。这些“提前立规矩、不让僵局发生”的思路,就是死锁预防(Deadlock Prevention)的核心。其中,资源有序分配、Wait-Die 与 Wound-Wait 是数据库并发控制中最经典的三套预防协议。 这篇文章就像一份“厨房管理手册”:先带你认识死锁是怎么悄悄发生的,再看三套预防规矩各自怎么运转、各有什么代价,最后教你根据饭店的客流特点(冲突强度、事务长短)选出最合适的那一套。

岗位关联

死锁预防是数据库内核和分布式存储系统工程师的必修课。掌握资源排序与时间戳协议,你才能在高并发锁密集型系统中提前阻断环路等待,设计出既稳又快的并发控制方案。

目录
  1. 可视化演示
  2. 死锁问题根源
  3. 预防思想总览
  4. 资源有序分配
  5. Wait-Die协议
  6. Wound-Wait协议
  7. 策略对比选型
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

死锁问题根源

后厨最怕的不是菜做不完,而是两个人扛着工具互不相让:A握刀要锅,B端锅要刀,谁先松手谁吃亏。这就是死锁的微缩模型——每个厨师都是“有把握、有念想、不放、互相等”。

数据库的并发控制里,两个事务同时更新两张表也会出现一模一样的僵局。数据库专家把这幅惨状拆成了四个缺一不可的“性格特征”,统称死锁四必要条件:互斥(一条数据同一时刻只允许一个事务使用,像一把菜刀不可能同时给两个人剁肉);持有并等待(事务拿着已获得的锁、又去申请别的锁,像握着菜刀不撒手还向别人借炒锅);不可抢占(锁只能由持有者主动释放,别人不能劈手去抢);环路等待(多个事务形成“你等我、我等它、它等你”的环形链)。

四个条件同时成立,死锁才会发生。这给了我们一个漂亮的突破口:只要打破其中任意一个,死锁就从“可能”变成“不可能”。后面所有预防策略,本质上都是选择性地拆掉某根柱子。

四个条件就像撑起一间“死锁屋”的四根柱子,后面所有预防策略都是定向拆柱——拆掉任意一根,屋顶就塌不下来。

死锁  ⟺  C1∧C2∧C3∧C4\displaystyle \text{死锁} \iff C_1 \land C_2 \land C_3 \land C_4
四个条件像四把锁一起锁住门:只有全部插到位,死锁才成立;缺一把门都锁不上。
  1. 四条件合取互斥、持有并等待、不可抢占、环路等待四个条件同时成立,死锁才出现。
    C1∧C2∧C3∧C4C_1 \land C_2 \land C_3 \land C_4
  2. 互斥 C1每个资源同一时刻只能被一个事务独占使用。
    资源→唯一持有者\text{资源} \to \text{唯一持有者}
  3. 持有并等待 C2事务不释放已持有的锁,又去向别的资源申请锁。
    持有 S+等待 T(S∩T=∅)\text{持有}\,S + \text{等待}\,T \quad (S \cap T = \emptyset)
  4. 不可抢占 C3锁只能由持有者自愿释放,其他事务无法强行夺走。
    释放=持有者主动动作\text{释放} = \text{持有者主动动作}
  5. 环路等待 C4事务等待图中存在一条首尾相接的有向环。
    T1→T2→⋯→Tk→T1T_1 \to T_2 \to \cdots \to T_k \to T_1

预防思想总览

既然四根柱子一起撑着死锁屋,管理员只需“拆一根”就能让屋子盖不起来。最省事的拆法有三类:让厨师一次把做菜需要的工具全备齐(破除持有并等待);允许管理员在紧急时收回闲置工具(破除不可抢占);规定按编号单向取工具或按资历定优先级(破除环路等待)。

预防是“事前立法”,区别于死锁检测的“事后执法”。InnoDB 默认用锁等待超时(innodb_lock_wait_timeout)来兜底:等锁超过阈值就报错回滚。这就像厨房已经堵死了才喊保安来疏导;而预防协议是在加锁规则层面就把环路从“概率事件”变成“不可能事件”,不需要建立等待图,也没有检测延迟。

本文的主角是三条预防路线:资源有序分配直接对“环路等待”动手;Wait-Die 与 Wound-Wait 用时间戳给事务排定“资历”,让等待关系严格单向化,同样是破除环路。它们的共同点是不需要事后救火,规则前置。

检测像“堵车后交警来疏导”,预防像“入口挂上单向行驶标志”。好的厨师长从不等到厨房瘫了才动手,而是进门就把规矩立好。

¬C1∨¬C2∨¬C3∨¬C4⇒无死锁\displaystyle \neg C_1 \lor \neg C_2 \lor \neg C_3 \lor \neg C_4 \Rightarrow \text{无死锁}
四根柱子不必全拆,拆掉任意一根,死锁屋就盖不起来。
  1. 否定运算某个必要条件不成立,柱子被拆掉一根。
    ¬Ci\neg C_i
  2. 析取连接只要有一根柱子倒下即可,不要求全部拆除。
    ¬C1∨¬C2∨¬C3∨¬C4\neg C_1 \lor \neg C_2 \lor \neg C_3 \lor \neg C_4
  3. 蕴含结论满足“至少破一个”就能推出系统无死锁。
    ⇒无死锁\Rightarrow \text{无死锁}

资源有序分配

管理员最简单的一招:给每件厨具钉上独一无二的编号,1号炒锅、2号汤勺、3号砧板……规矩只有一条,拿工具必须从小编号往大编号拿,拿了2号就绝不能再回头拿1号。

换成数据库语言,“资源编号”可以对应行号、索引键、分区号。任何事务要加锁,必须沿着编号递增的方向申请。这样等待关系永远是“编号小的事务 → 编号大的事务”,不可能绕成环——因为环上必然存在一次“回头拿小号”的机会,而规则恰好禁止了它。这条规则无回滚、无抢占、无饥饿,实现成本极低。

但它的代价同样明显:资源编号一旦定下就全局固定,很难与业务自然的加锁顺序一致。比如业务习惯“先扣库存再写订单”,编号却规定先写订单再扣库存,开发者只能别扭地改变加锁顺序。为了硬凑顺序,还可能被迫多申请一些本不需要的资源,造成“锁升级”,反而降低并发度。

这规矩像“进厨房一律往编号更大的方向拿”,简单粗暴;但厨师想先拿炒锅再拿铲子时就会骂娘。管理者必须权衡规矩的简单和厨师的顺手。

持有 Ri  ⇒  仅可申请 Rj(j>i)\displaystyle \text{持有}\, R_i \;\Rightarrow\; \text{仅可申请}\, R_j \quad (j > i)
持有小编号资源后,只能再伸手去拿编号更大的资源,方向单向、不可回头。
  1. 前提持有事务已经握着编号较小的某个资源。
    持有 Ri\text{持有}\, R_i
  2. 方向约束下一步只能申请编号更大的资源。
    j>i⇒可申请 Rjj > i \Rightarrow \text{可申请}\, R_j
  3. 无环结论等待边始终从小编号指向大编号,无法闭合为环。
    R1→R2→⋯→Rn⇒无环R_1 \to R_2 \to \cdots \to R_n \Rightarrow \text{无环}

Wait-Die协议

后来厨房来了位资历很老的厨师,管理员换了一套“按资历”的规矩。每位厨师进门领一个“进门号”——时间戳 TS,号越小资历越老。Wait-Die 的规则是:老厨师要用的工具被年轻厨师占着,老厨师站在旁边等;年轻厨师要用的工具被老厨师占着,年轻厨师立刻放下所有工具、重新排队。

技术化表述:事务 Ti 请求被 Tj 持有的锁时,若 TS(Ti) < TS(Tj),Ti 等待;否则 Ti 回滚(die)。注意回滚的一定是“年轻的请求者”,老事务永远不必回滚。这保证“资历越老越安全”——占着资源的人不用怕,伸手要资源的人反而可能被劝退。

Wait-Die 是非抢占式的:老事务从不夺锁,只是等。它无死锁的证明很干净:若 Ti 等待 Tj,必然 TS(Ti) < TS(Tj),等待关系沿时间戳严格递减;若存在环,绕一圈就会出现 TS(T1) < TS(T2) < … < TS(T1) 的矛盾。代价是年轻事务可能反复 die、反复重来,整体回滚率偏高,尤其在高冲突场景下。

Wait-Die 像“年轻人给老师傅让灶台”:老师傅说“我等会儿就行”,年轻人一看老师傅要用,赶紧收料重新排队。有礼貌,但年轻人来回折腾很费功夫。

TS(Ti)<TS(Tj)  ⇒  Ti 等待;否则  ⇒  Ti 回滚\displaystyle \text{TS}(T_i) < \text{TS}(T_j) \;\Rightarrow\; T_i \text{ 等待};\qquad \text{否则} \;\Rightarrow\; T_i \text{ 回滚}
老请求者排队等,年轻请求者主动回滚让路。
  1. 比较时间戳请求者时间戳小于持有者,说明请求者资历更老。
    TS(Ti)<TS(Tj)\text{TS}(T_i) < \text{TS}(T_j)
  2. 老请求者等待不夺锁,原地排队,避免无畏回滚。
    Ti 等待T_i \text{ 等待}
  3. 年轻请求者回滚释放自己所有锁,从头再来。
    Ti 回滚 (die)T_i \text{ 回滚 (die)}

Wound-Wait协议

Wound-Wait 是 Wait-Die 的“镜像版本”,把优先级反转过来:老厨师绝不等待,他看上的工具可以直接从年轻人手里拿走(wound,伤害),年轻人只能收手回滚;只有年轻厨师要用的工具被老厨师占着时,年轻人才等待。

技术化表述:若 TS(Ti) < TS(Tj),Ti 伤害 Tj——Tj 回滚并释放所有锁,Ti 立即获得锁;否则 Ti 等待。与 Wait-Die 的关键差异是“谁是回滚者”:Wait-Die 回滚年轻的请求者,Wound-Wait 回滚年轻的持有者。

Wound-Wait 是抢占式的,需要底层锁管理器具备“强制释放某事务未完成锁”的能力,意味着 undo 日志和锁表要能精确清理。它天然无饥饿:任何老事务都不会被回滚,只会不断抢到所需资源并最终完成;代价是年轻事务可能反复被伤害,产生级联回滚。

Wound-Wait 像“老师傅直接端走年轻人的锅”,高效但霸道。年轻人被赶走几次后,后厨的菜可能洒一地——回滚写日志的开销就是那些洒落的菜。

TS(Ti)<TS(Tj)  ⇒  Ti 伤害 Tj;否则  ⇒  Ti 等待\displaystyle \text{TS}(T_i) < \text{TS}(T_j) \;\Rightarrow\; T_i \text{ 伤害}\, T_j;\qquad \text{否则} \;\Rightarrow\; T_i \text{ 等待}
老请求者直接伤害年轻持有者,年轻请求者只能老实等待。
  1. 比较时间戳先判断谁老谁少,这是协议的唯一裁判标准。
    TS(Ti)<TS(Tj)\text{TS}(T_i) < \text{TS}(T_j)
  2. 老请求者伤害强制年轻的持有者回滚,自己立刻获得锁。
    Ti 伤害 TjT_i \text{ 伤害 } T_j
  3. 年轻请求者等待让老事务先完成,自己排队候场。
    Ti 等待T_i \text{ 等待}

策略对比选型

三套预防制度没有绝对好坏,只有“合不合适”。选择的两个关键指标是回滚率(年轻人被劝退或赶走的次数)和等待开销(师傅们站着不干活的时间)。

从回滚率看,Wound-Wait 优于 Wait-Die:Wait-Die 中年轻事务可能反复 die,积累的工作统统作废;Wound-Wait 中时间戳越小越安全,老事务永远不被回滚,回滚集中在刚起步的新事务上,整体损失更小。从等待开销看,Wait-Die 不抢占、不需要强制资源回收,锁管理器实现更简单;而 Wound-Wait 的每次抢占都要清理被伤害事务的 undo 状态,引擎层成本更高。

工程实际中,InnoDB 这类单机数据库并不把预防当默认方案,而是靠 MVCC(多版本并发控制)+ 锁等待超时 + 死锁检测组合兜底:MVCC 让读写互不阻塞,锁冲突范围大幅缩小,死锁概率天然下降。但在分库分表、分布式数据库(如 TiDB、OceanBase)里,跨节点的锁等待关系无法靠单一等待图实时检测,基于时间戳的 Wait-Die / Wound-Wait 就派上用场,成为“以回滚换无死锁”的兜底协议。

小饭馆(单机)客流低,用“简单提醒 + 堵了再疏导”就够了;连锁大饭店(分布式)必须一开始就用资历规矩。选哪套制度,看客流多少(冲突强度)和师傅贵不贵(事务成本)。

回滚率WW≤回滚率WD;抢占开销WW≥抢占开销WD\displaystyle \text{回滚率}_{\text{WW}} \le \text{回滚率}_{\text{WD}};\qquad \text{抢占开销}_{\text{WW}} \ge \text{抢占开销}_{\text{WD}}
Wound-Wait 用更高的抢占开销换来更低的回滚率;Wait-Die 反之。
  1. 回滚率比较Wound-Wait 回滚集中于年轻新事务,整体回滚率更低。
    回滚率WW≤回滚率WD\text{回滚率}_{\text{WW}} \le \text{回滚率}_{\text{WD}}
  2. 抢占开销比较Wait-Die 非抢占、无强制回收成本,等待更多。
    抢占开销WW≥抢占开销WD\text{抢占开销}_{\text{WW}} \ge \text{抢占开销}_{\text{WD}}

AI 应用场景

分布式数据库内核:用时间戳协议根除跨节点死锁

阶段:存储引擎并发控制层

解决的问题:跨节点分布式事务的锁等待关系分散在多个节点上,没有全局等待图可依赖;传统集中式死锁检测器在集群规模扩大后延迟高、容易误判。基于时间戳的 wound-wait 协议让每个锁管理器都能独立判断“该让谁等、该伤害谁”,无需全局同步即可根除环路等待。

具体案例:TiDB / OceanBase 在悲观事务模型中面对热点行冲突时,会记录事务启动时间戳;当多个事务争抢同一行,内核按时间戳比较决定谁等待、谁回滚,避免多个存储节点之间形成跨节点死锁环。AI 大模型训练任务高频读写参数表时,这类机制保障了分布式事务不因锁环而集体超时。

电商库存扣减:按固定顺序加锁防死锁

阶段:业务应用层加锁顺序设计

解决的问题:订单服务通常要同时更新“库存表”和“订单明细表”。如果服务A先锁库存再锁订单,服务B先锁订单再锁库存,冲突时就会互相持锁等待。AI 大促预测系统能提前估算峰值流量,架构师借此在代码中统一“仓库 → 商品 → 订单”的加锁顺序,从源头杜绝订单模块的死锁告警。

具体案例:某电商在秒杀场景下预先按“仓库ID + 商品ID”排序加锁,把死锁率从日均数十次降到接近零,同时配合超时重试兜底,大促期间订单接口的 p99 延迟保持稳定。

AI特征存储写入:wait-die 防止训练任务互相等待

阶段:特征平台写入调度

解决的问题:多个训练任务同时回填特征组版本,底层用行锁保护版本元数据。不同任务启动时间不同,用 wait-die 让“后启动的任务”遇到老任务持有版本锁时自动回滚重试,避免两个任务各持一行互等、阻塞整条特征流水线。

具体案例:某 MLOps 平台将特征写入改为 wait-die 语义后,训练任务并发写同一特征组时不再出现“双双超时”,任务完成时长的方差明显下降,模型上线前的特征刷新效率提升了约 40%。

Python 演示

python63 行
"""Deadlock prevention simulation: wait-die vs wound-wait"""  class Transaction:    def __init__(self, name, ts):        self.name = name        self.ts = ts            # 时间戳, 越小代表事务越老        self.holds = []         # 已持有的资源列表  class LockManager:    def __init__(self, protocol):        self.protocol = protocol    # 'wait-die' 或 'wound-wait'        self.owners = {}            # 资源 -> 持有事务        self.events = []            # 事件日志     def request(self, tx, res):        holder = self.owners.get(res)        if holder is None:            self.owners[res] = tx            tx.holds.append(res)            self.events.append('%s gets %s' % (tx.name, res))            return 'grant'        if holder is tx:            return 'grant'        if self.protocol == 'wait-die':            if tx.ts < holder.ts:                self.events.append('%s waits %s(holder %s)' % (tx.name, res, holder.name))                return 'wait'            self._abort(tx, 'die')            return 'abort'        if tx.ts < holder.ts:            self._abort(holder, 'wound')            self.owners[res] = tx            tx.holds.append(res)            self.events.append('%s steals %s' % (tx.name, res))            return 'grant'        self.events.append('%s waits %s(holder %s)' % (tx.name, res, holder.name))        return 'wait'     def _abort(self, tx, reason):        self.events.append('%s aborts(%s)' % (tx.name, reason))        for r in list(tx.holds):            self.owners.pop(r, None)        tx.holds = []  def simulate(protocol):    lm = LockManager(protocol)    t1 = Transaction('T1', ts=1)   # 老事务    t2 = Transaction('T2', ts=2)   # 年轻事务    lm.request(t1, 'R1')    lm.request(t2, 'R2')    lm.request(t1, 'R2')           # 老事务申请被年轻事务持有的锁    lm.request(t2, 'R1')           # 年轻事务申请被老事务持有的锁    print('=== %s ===' % protocol)    for e in lm.events:        print(' ', e)  if __name__ == '__main__':    simulate('wait-die')    simulate('wound-wait')
目的:用同一个冲突场景运行两套协议,直观看出 wait-die 回滚“年轻请求者”、wound-wait 回滚“年轻持有者”的行为差异。
  1. self.owners = {}一张“资源登记表”,像每个厨具挂着“谁在用”的牌子,锁管理器靠它判断冲突。
  2. if tx.ts < holder.ts:时间戳比较是两套协议唯一的裁判标准:号小者老、号大者少。
  3. self._abort(tx, 'die')wait-die 让年轻的请求者回滚,放手所有资源重新排队。
  4. self._abort(holder, 'wound')wound-wait 让老的请求者把年轻的持有者赶走。
  5. if __name__ == '__main__':直接运行这个文件,就能看到同一冲突下两套协议的分岔结局。

常见误区

误区“我配置了 innodb_lock_wait_timeout 就是在做死锁预防吧?超时后解锁就是预防啊。”

正解预防是在阻塞发生之前,用规则让环路等待根本不可能形成;检测是死锁已经发生后,再通过超时或等待图把它打断。好比预防是“路口红绿灯让车辆按序通行”,检测是“堵死后交警来疏导”。两者介入时机完全不同。

为何常见:很多数据库服务名义上都在“处理死锁”,用户只看到“报错 → 重试”的循环,容易把事后恢复误当成事前预防。

误区“Wait-Die 规则里谁都可以 die,老事务遇到冲突也可能回滚吧?”

正解Wait-Die 中只有“请求者”可能 die,而且只有“年轻的请求者”才 die。老事务(时间戳小)请求锁时总是选择等待,它持有的锁被年轻事务请求时,年轻事务 die,老事务毫发无损。老事务从不被回滚。

为何常见:协议名里既有 wait 又有 die,容易想当然地认为两边都可能回滚;实际必须严格按时间戳比较“谁请求、谁持有”来判断。

误区“Wound-Wait 回滚率更低,老事务又不被回滚,直接用就对了。”

正解Wound-Wait 有抢占开销:老事务夺锁时,年轻事务已做的修改要回滚,undo 日志、二级索引变更都要额外清理;被伤害的年轻事务可能反复回滚,产生级联效应,在极端冲突下吞吐未必更好。实现上也要求锁管理器支持“强制释放”,复杂度更高。

为何常见:看到“老不等待、老不死”的直观优势,忽略了抢占动作在引擎层的真实成本,属于典型的“只看收益不看代价”。

误区“只要大家商量好一个加锁顺序就可以了吧?比如先表后行?”

正解资源有序分配要求所有资源有全局唯一编号、所有事务都严格按同一递增方向申请,且持有大编号资源后不能再回头拿小编号资源。只约定“先表后行”而跨表顺序没有统一全局编号,照样会在多表场景下形成环路死锁。

为何常见:“约定顺序”听起来像口头沟通,很容易被理解成局部规则;实际上它是一个强制的全局约束,任何事务违规都不行。

面试题

Q1. 以下哪一项不属于死锁的必要条件?

单选题 · easy

  • A.互斥
  • B.持有并等待
  • C.时间戳单调递增← 正确答案
  • D.环路等待

答案:C。死锁四必要条件是互斥、持有并等待、不可抢占、环路等待。时间戳只是 Wait-Die / Wound-Wait 用来给事务排“资历”的工具,属于预防手段,不是僵局本身的前提。就像“进门拿号”是餐厅管理规则,不是厨房堵车发生的必要条件。

Q2. 资源有序分配主要打破哪个必要条件来预防死锁?

单选题 · easy

  • A.互斥
  • B.持有并等待
  • C.不可抢占
  • D.环路等待← 正确答案

答案:D。所有事务按编号递增申请资源,等待关系只能从小到大单向延伸,无法绕成闭合环,所以直接消灭了“环路等待”。互斥和不可抢占它都没有改变;持有并等待依然是允许的。

Q3. Wait-Die 协议中,事务 T1(时间戳 5)请求被 T2(时间戳 3)持有的锁,会发生什么?

单选题 · mid

  • A.T1 等待
  • B.T1 回滚← 正确答案
  • C.T2 回滚
  • D.两个事务一起回滚

答案:B。时间戳 5 比 3 大,说明 T1 更年轻。Wait-Die 的规则是“老的等待、少的 die”:请求者是年轻事务,直接回滚。可以想象成进门号大(后进厨房)的厨师没有资格让进门号小(先进厨房)的厨师让路。

Q4. Wound-Wait 协议中,事务 T1(时间戳 2)请求被 T2(时间戳 7)持有的锁,会发生什么?

单选题 · mid

  • A.T1 等待
  • B.T1 回滚
  • C.T2 回滚← 正确答案
  • D.锁自动升级为表锁

答案:C。T1 的时间戳 2 < 7,是老事务。Wound-Wait 的规则是“老的伤害少的”:老请求者直接伤害年轻的持有者,T2 被迫回滚并让出锁。就像老师傅直接端走年轻厨师的锅。

Q5. 关于 Wait-Die 协议,下列说法正确的有?

多选题 · mid

  • A.老事务请求锁时选择等待← 正确答案
  • B.年轻事务请求锁时回滚← 正确答案
  • C.被回滚的一定是资源的持有者
  • D.它是非抢占式协议← 正确答案

答案:ABD。Wait-Die 中回滚的是“年轻的请求者”,不是持有者,所以 C 错。它从不强行夺锁,只是等待或主动回滚,属于非抢占式协议;老事务永远只等不 die,年轻事务被回滚后要释放全部已持锁。

Q6. 下列哪种策略属于抢占式死锁预防?

单选题 · mid

  • A.资源有序分配
  • B.Wait-Die
  • C.Wound-Wait← 正确答案
  • D.预取全部锁

答案:C。Wound-Wait 中老事务可以直接从年轻持有者手中夺锁,是标准的抢占式协议。Wait-Die 只等不抢;资源有序分配靠申请顺序约束;预取全部锁靠“一次拿齐”避免等待。抢占能力并非所有锁管理器都支持。

Q7. 关于 Wound-Wait 协议,正确的说法有?

多选题 · hard

  • A.老事务能抢占年轻事务的锁← 正确答案
  • B.年轻事务可能被反复回滚← 正确答案
  • C.老事务也可能被回滚
  • D.老事务不会饿死← 正确答案

答案:ABD。老事务只伤人不被伤,C 错。任何老事务请求时总能赶走年轻的持有者,最终一定拿到所需锁,所以不会饿死;年轻事务则可能在多次冲突中被反复 wound,回滚次数较多,这是 Wound-Wait 的主要代价。

Q8. Wait-Die 协议为什么不会形成死锁?

单选题 · hard

  • A.等待链上时间戳严格递减,闭合环会产生矛盾← 正确答案
  • B.老事务从不等锁,直接抢
  • C.系统用超时打断所有等待
  • D.锁会被自动降级

答案:A。Wait-Die 中若 Ti 等待 Tj,则 TS(Ti) < TS(Tj),沿等待链时间戳严格递减;若存在环,绕一圈会出现 TS(T1) < … < TS(T1) 的矛盾,因此环不可能存在。它不需要超时参与,老事务也不是“不等锁”,恰恰是“老的在等”。

Q9. 资源有序分配在实际工程中的局限有哪些?

多选题 · mid

  • A.全局编号难以统一← 正确答案
  • B.加锁顺序可能与业务冲突← 正确答案
  • C.一定会产生大量回滚
  • D.可能被迫多取资源降低并发← 正确答案

答案:ABD。资源有序分配本身没有回滚,C 错。资源编号一旦全局固定,业务想先操作“大编号”资源就得绕路;为了硬凑顺序还可能多申请本不需要的资源(锁升级),从而降低并发度。这些局限让它适合编号稳定、顺序天然一致的场景。

Q10. 系统中长事务多且回滚成本高昂时,通常更推荐哪种预防策略?

单选题 · extreme

  • A.资源有序分配
  • B.Wait-Die
  • C.Wound-Wait← 正确答案
  • D.只依赖超时检测

答案:C。Wound-Wait 保证老事务永远不被回滚且总能抢占到底,长事务(时间戳小)一旦进入系统就“一老永逸”。Wait-Die 中老事务虽不 die,却可能长时间等待年轻事务;资源排序若与业务冲突则用不起来;超时检测只是事后兜底,无法预防死锁。