← 每日面试题 学习看板

使用分布式锁时,分布式锁加在事务外面还是里面,有什么区别?

面试题/01实践场景题/实践场景题目/场景与业务实践/使用分布式锁时,分布式锁加在事务外面还是里面,有什么区别?.md

使用分布式锁时,分布式锁加在事务外面还是里面,有什么区别?

完整讲解(权威版)

最后更新:2026-10-28 · 基于第 1 次作答

L1 · 30 秒速记

结论:锁必须放在事务外面——事务内加锁会持锁等待占住 DB 连接、放大锁粒度,甚至耗尽连接池;且解锁时机要在事务提交之后。 关键词:锁在事务外 · 连接池占用 · 锁粒度 · 提交先于解锁 边界:任何"锁内做远程调用/慢查询"都应避免;锁粒度按业务键,不要用全局锁。

L2 · 核心讲解

核心考点定位:考察"锁与事务的边界"——锁要覆盖业务互斥段,但不能覆盖持锁等待与远程调用;面试官想排除"锁放事务内更安全"的错误认知,以及"提交后才解锁"的时序误区。

机制链(≥3 环):

  1. 锁放事务内且未持有锁 → 线程阻塞在获取锁:此时事务已开启,持有一个 DB 连接空转等待,锁竞争加剧时连接被大量占用。
  2. 事务内持锁等待 → 连接池连接被占住:并发上来后 maxTotal 耗尽,其他请求在 getConnection() 排队,吞吐骤降甚至雪崩。
  3. 锁内做远程调用/慢查询 → 锁粒度被放大:临界区变长,其他线程等待更久,锁超时(watchdog 也难持续续期)。
  4. 解锁在事务提交前 → 其他线程读到旧数据:提交未完成锁已释放,B 线程进临界区读到 A 未提交数据,破坏一致性。

证据锚点(去重 ≥6):

对比与权衡:

方案一致性吞吐失败模式适用边界
锁在事务内低低持锁等待占连接、锁粒度放大仅单机无锁竞争
锁在事务外 + @Transactional中高中提交在解锁后,B 可能读旧读已提交可接受
TransactionTemplate 手动控制高中高需手动提交/回滚需严格"提交先于解锁"

常见误区:

  1. 错误:锁放事务内更安全,因为锁和事务一起结束。→ 正确:锁要在事务外,事务内持锁等待会占 DB 连接并放大锁粒度。→ 为什么:锁是互斥资源,事务是数据一致性单元,两者生命周期不同。
  2. 错误:事务提交后锁自动释放。→ 正确:锁必须显式在 finally 中 unlock,且提交要在解锁前。→ 为什么:@Transactional 不管理 Redis 锁,锁与事务是两套机制。
  3. 错误:锁粒度越大越安全。→ 正确:按业务键 lock:order:{orderId} 锁,避免全局锁。→ 为什么:大锁让无关请求互相排队,吞吐骤降,且单个热点 key 成瓶颈。

适用边界:若业务无需强互斥(如只读校验),可只用 DB 乐观锁;若锁内必须做远程调用,应把预校验放锁外、锁内只做关键写,或用 MQ 串行化替代;强一致且不能接受读旧,必须用 TransactionTemplate 控制"提交 → 解锁"顺序。

延伸追问(≥3)

  1. 为什么解锁放在事务提交前会导致读到旧数据?
  1. 事务内持锁等待为什么危险?
  1. 锁内做远程调用有什么问题?

面试者参考回答

【结论】 分布式锁必须放在事务外面——事务内加锁会持锁等待占住 DB 连接、放大锁粒度,严重时耗尽连接池;且锁释放与事务提交的时序必须"提交先于解锁"。

【三点】 第一,放事务内的问题:未持有锁时线程阻塞在加锁,但事务已开启、占着一个连接空等;并发上来连接池 maxTotal 被占满,其他请求 getConnection() 超时排队,吞吐骤降甚至雪崩。第二,放事务外且顺序正确:先 tryLock(带等待与租约,如 tryLock(2, 30, SECONDS))→ 开启事务 → 业务写 → 提交 → 在 finally 中 unlock;推荐用 TransactionTemplate 手动控制提交,确保提交完成在解锁前,避免其他线程读到未提交的旧数据。第三,锁粒度与锁内操作:按业务键 lock:order:{orderId} 锁,锁内只做必要写,不做远程调用/慢查询,否则临界区变长、锁超时(watchdog 续期也扛不住远程阻塞)。

【边界】 如果业务可接受短暂读旧或仅做校验,可退化为 DB 乐观锁;若锁内必须远程调用,应预校验在锁外、锁内只写关键路径,或用 MQ 串行化替代。任何方案都要保证解锁在 finally,且提交先于解锁。

面试记录 2026-10-28(第 1 次)

题目:使用分布式锁时,分布式锁加在事务外面还是里面,有什么区别?

难度:low

标签:practice/business, distributed/lock, general/knowledge

评分:⭐⭐⭐☆☆(3/5)

我的回答

使用分布式锁时,分布式锁要放在事务外部,不能放在事务内部,放在事务内,分布式情况下,每个服务都开启了自己的事务,造成多事务的阻塞,把压力都转到了数据库,可能会造成大量阻塞;放在事务外侧,才能有效阻断分布式环境下多端点之间的竞争,减少事务资源浪费;并且在事务内会让事务过程中额外产生远程调用,将事务的范围扩大影响事务执行,并且在事务内如果没有持有到锁导致阻塞等待,会直接阻塞一个数据库链接,最终可能使得数据库无法使用链接全部被占用,一定不要再事务内做锁;

点评

评分:⭐⭐⭐☆☆(3/5)

优点:结论正确锁放事务外、讲出事务内持锁等待阻塞 DB 连接可能耗尽连接池、讲到事务范围扩大。

不足:没讲锁释放时机 vs 事务提交的竞态(提交发生在解锁之后会出现 B 读旧数据)、没提正确姿势(lock→事务→提交→unlock 且解锁放 finally、TransactionTemplate 手动控制)、没讲锁内做远程调用放大锁粒度与死锁风险。

补充:核心结论不变但要补"解锁≠提交完成"的竞态(推荐 TransactionTemplate 手动控制提交在解锁前)。

完整讲解见上方「完整讲解(权威版)」