通用技术面试:Serializable 隔离保证什么,为什么事务仍会重试?
题干与适用场景
面试官给你两个并发事务:它们读取相同业务规则,却更新不同记录。两边都看到“名额足够”,最后总状态违反约束。请解释较弱隔离级别会发生什么、Serializable 如何阻止结果,以及应用如何处理失败。
这道题考察数据库并发控制的概念边界。回答应区分快照可见性、锁等待、冲突检测和业务重试,不能只背四个隔离级别名称。
面试官考察什么
面试官关注你能否用一个具体交错顺序解释异常,能否区分“结果等价于某个串行顺序”和“所有事务真的排队执行”,以及能否说明失败是正确保护的一部分。Amazon 的产品面试指引要求候选人深入指标与决策依据;技术回答同样需要把保证、代价和应用动作说清楚。
回答前要澄清的问题
先问数据库实现、默认隔离级别、读写模式、约束是否能由数据库表达,以及事务失败后调用方能否安全重做。PostgreSQL 的 Repeatable Read 使用事务快照;Serializable 在此基础上检测可能的序列化异常并终止其中一个事务。不同数据库的实现和默认行为不能直接互换。
30 秒回答框架
先给结论:Serializable 要求已提交结果等价于某个串行执行,但不代表事务全部串行排队。然后用两个事务的写偏斜交错说明 Repeatable Read 的边界,再说明 Serializable 如何检测危险冲突并返回序列化失败。最后给出重试整个事务、限制次数、保持幂等和监控冲突率的做法。
分步骤深入分析
第一步:用交错顺序描述异常
假设规则是“至少一名值班医生保持在岗”。事务 A 读取医生 B 在岗,事务 B 读取医生 A 在岗;A 将自己设为休假,B 也将自己设为休假。每次事务都只更新自己的行,因而没有直接写写冲突,但最终无人值班。这是写偏斜,说明每个事务看到的快照正确,不代表组合后的业务不变量被保留。
第二步:区分快照稳定性与串行等价
Repeatable Read 让同一事务的查询看到稳定快照,不读取后来提交的并发变化;它不保证所有读写集合可以排列成一个满足业务规则的串行顺序。快照隔离通常提高并发,但应用必须知道它保护了哪些异常、没有保护哪些异常。
第三步:说明 Serializable 的保证
Serializable 的目标是让已提交结果等价于某个一次只执行一个事务的顺序。实现可以通过锁、冲突检测或 Serializable Snapshot Isolation 完成,不等于每个读都阻塞其他事务。PostgreSQL 会监控读写依赖,发现可能形成序列化异常时让一个事务失败。
第四步:解释为什么要重试整个事务
序列化失败发生在提交或接近提交时,数据库不能替你猜测业务代码是否可重做。应用应回滚当前事务,从读取第一条数据开始完整重跑,而不是只重发最后一条 UPDATE。重试应有上限和退避;外部副作用要放在提交后,或用幂等键和可靠事件记录隔离。
第五步:比较代价与选择
Serializable 可能增加冲突、重试、内存或锁管理开销,吞吐量也会受访问模式影响。对余额、库存、配额等强不变量场景,可接受更高隔离和重试成本;对只读报表或允许短暂不一致的查询,较弱级别可能更合适。选择要由不变量、并发度、延迟目标和失败处理能力共同决定。
高质量示范回答
Serializable 的保证是:所有已提交事务的结果,都等价于某个串行执行顺序;它不要求数据库把事务全部排队。以“至少一名医生在岗”为例,两个事务在 Repeatable Read 下都读到另一名医生在岗,然后分别把自己设为休假。它们没有更新同一行,却共同破坏了不变量,这就是写偏斜。
Serializable 会检测这种读写依赖可能形成的序列化异常,并让其中一个事务以 serialization failure 结束。应用必须回滚并从事务开始处重试,设置有限次数和退避;发送邮件、扣款等外部副作用应在提交后处理,或使用幂等键。对库存、余额和配额我会优先选择能保护业务不变量的隔离级别;对允许近似的报表则评估更低级别的吞吐收益,并用监控验证异常率。
常见错误与改进
- 说 Serializable 等于全局排队:改为“结果等价于某个串行顺序”。
- 只说加锁:补充冲突检测和数据库实现差异。
- 只重试最后一条 SQL:必须从事务开始完整重跑。
- 忽略副作用:说明提交后事件、幂等键或去重表。
- 把 Repeatable Read 说成永远没有异常:明确写偏斜或序列化异常仍可能存在。
追问及应对
Why can a read-only transaction still fail at Serializable?
Its reads can participate in dependencies that make another transaction’s committed result non-serializable. The database may abort a transaction to preserve the global guarantee; the application should treat the error as retryable when the operation is safe to repeat.
Should every request use Serializable?
No. Start with the business invariant and workload. Use the stronger level where an anomaly is unacceptable, and choose a weaker level where approximate reads are acceptable and the performance trade-off is measured.
Why must the retry include all reads?
The conflict decision depends on the complete read and write set. Replaying only the final write can use stale assumptions and recreate the same anomaly.
How do you observe whether retries are healthy?
Track serialization failures, retry success rate, retry latency, exhausted attempts, and user-visible errors by transaction type. A high retry rate can indicate a hot invariant or an access pattern that needs redesign.