SWE · Coding / Database

TikTok 条件更新:Version Check、幂等键与事务回滚

定义版本和旧值前置条件,在一个事务内保存更新与幂等结果,分析并发重试和外部副作用。

题目重点:定义版本和旧值前置条件,在一个事务内保存更新与幂等结果,分析并发重试和外部副作用。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练请求包含entryId、expectedVersion、expectedValue、newValue和幂等键;版本与旧值两个条件都要通过。
  • 成功值写入并将版本加1;失败不修改条目。一个幂等键限定到调用者与目标条目,同键不同请求内容报冲突。
  • 训练条目、幂等记录、必要outbox位于同一支持事务的数据库;跨库/HTTP副作用另讨论。
  • 对确定业务成功或前置条件失败持久化响应;解析失败、鉴权失败和暂时数据库故障不在本训练的持久化范围内。

解题路线

  1. 先把模糊的check变成明确谓词:id存在且当前version=expectedVersion且当前value=expectedValue。版本比较检测自上次读取后是否发生更新;旧值比较是业务前提,两者不能未经确认写成OR。
  2. 幂等记录应保存请求指纹、处理状态和完整稳定响应,不能只保存见过这个key。重试时指纹相同返回已记录结果,不再次version check;否则一次成功后版本已变,会把成功重试错误地变成失败。
  3. 更新与幂等结果必须在同一事务中提交。若先更新后写幂等记录,进程在两步之间崩溃就失去重试依据;若先标成功再更新,另一请求可能读到尚未发生的结果。事务提交是内部原子性的边界。
  4. 并发同键用唯一约束或锁协调,创建请求记录后按固定顺序锁目标行,再检查并更新;重复请求等待首个事务完成,然后读已提交结果。数据库冲突/死锁要做有界重试,不能拿一个永久processing标记当答案。
  5. 不同键针对同版本并发更新时,目标行锁或条件UPDATE使至多一方成功。可以执行WHERE id=? AND version=? AND value=?并检查影响行数;零行代表条件未满足,但若需要区分不存在/版本错/值错,应在同事务内取得稳定当前状态。
  6. value检查要定义比较:SQL NULL需显式语义,浮点相等脆弱,JSON可能有键序或类型差异。请求指纹同样要用规范编码,不能把同义JSON文本差别或模糊拼接导致的哈希冲突忽略。
  7. 事务内发生异常应回滚条目和本次未完成记录,重试可重新执行;如果业务前置失败属于已完成请求,可以提交失败响应而不改条目。这个区别解释为何并非所有失败都把幂等记录删除。
  8. 数据库rollback不能撤回已发邮件、支付或HTTP调用。将待执行副作用作为outbox与条目一起提交,由消费者幂等执行;跨系统不应承诺原子恰好一次,需用补偿、对账和结果查询处理不确定状态。
  9. 幂等键保留期决定承诺的重试窗口。过期后同键可能被视为新请求,必须向客户端明确;payload含敏感值时存指纹或脱敏响应,但仍要能稳定判断同键异请求。

手动走一遍

当前value=A、version=7,请求k以(A,7)改B成功并存响应version=8。响应丢失后重试k,直接返回成功8;新键k2仍以version7改C则失败。若不同请求抢同一键但newValue不同,应冲突。写B后事务异常未提交,条目仍A/7且无成功幂等记录。

规模与取舍

条目和幂等记录的点查/插入取决于索引,B树通常O(log N);同一热点条目必须串行化,吞吐上限受锁持有时间约束。幂等存储随保留窗口请求量增长;事务锁等待和重试不能被平均点查复杂度掩盖。

易错点与追问

容易失分的边界

  • 先读version再无条件UPDATE产生丢失更新。
  • 成功重试再次check导致返回版本冲突。
  • 幂等记录与条目分两次提交。
  • 以为数据库回滚能撤销外部HTTP副作用。

继续深挖

  • 失败响应是否缓存,哪些失败允许重试?
  • 键过期后怎样防止重放旧请求?
  • 如何处理两个不同条目互相锁导致死锁?
  • 跨库事务失败如何补偿与对账?
✓已复制微信号!