使用分布式锁时,分布式锁加在事务外面还是里面,有什么区别?
最后更新:2026-10-28 · 基于第 1 次作答
结论:锁必须放在事务外面——事务内加锁会持锁等待占住 DB 连接、放大锁粒度,甚至耗尽连接池;且解锁时机要在事务提交之后。 关键词:锁在事务外 · 连接池占用 · 锁粒度 · 提交先于解锁 边界:任何"锁内做远程调用/慢查询"都应避免;锁粒度按业务键,不要用全局锁。
核心考点定位:考察"锁与事务的边界"——锁要覆盖业务互斥段,但不能覆盖持锁等待与远程调用;面试官想排除"锁放事务内更安全"的错误认知,以及"提交后才解锁"的时序误区。
机制链(≥3 环):
maxTotal 耗尽,其他请求在 getConnection() 排队,吞吐骤降甚至雪崩。证据锚点(去重 ≥6):
@Transactional:声明式事务;锁需手动包在事务外,@Transactional 不管理锁。TransactionTemplate:编程式事务;可精确控制"提交 → 解锁"顺序,推荐用于锁内写。maxTotal / maxWaitMillis:连接池参数;持锁等待占连接时池会耗尽,getConnection() 超时。lockWatchdogTimeout=30000:Redisson 默认锁租约 30 秒;锁内慢操作可能触发续期压力。tryLock(2, 30, SECONDS):带等待时间与租约的加锁,避免无限阻塞占连接。unlock 放 finally:确保异常也能释放锁,防死锁。SELECT FOR UPDATE:DB 乐观/悲观锁替代方案;性能低但强一致。对比与权衡:
| 方案 | 一致性 | 吞吐 | 失败模式 | 适用边界 |
|---|---|---|---|---|
| 锁在事务内 | 低 | 低 | 持锁等待占连接、锁粒度放大 | 仅单机无锁竞争 |
锁在事务外 + @Transactional | 中高 | 中 | 提交在解锁后,B 可能读旧 | 读已提交可接受 |
TransactionTemplate 手动控制 | 高 | 中高 | 需手动提交/回滚 | 需严格"提交先于解锁" |
常见误区:
finally 中 unlock,且提交要在解锁前。→ 为什么:@Transactional 不管理 Redis 锁,锁与事务是两套机制。lock:order:{orderId} 锁,避免全局锁。→ 为什么:大锁让无关请求互相排队,吞吐骤降,且单个热点 key 成瓶颈。适用边界:若业务无需强互斥(如只读校验),可只用 DB 乐观锁;若锁内必须做远程调用,应把预校验放锁外、锁内只做关键写,或用 MQ 串行化替代;强一致且不能接受读旧,必须用 TransactionTemplate 控制"提交 → 解锁"顺序。
TransactionTemplate 保证顺序(提交成功后再 unlock)?getConnection() 超时、连接数监控)?【结论】 分布式锁必须放在事务外面——事务内加锁会持锁等待占住 DB 连接、放大锁粒度,严重时耗尽连接池;且锁释放与事务提交的时序必须"提交先于解锁"。
【三点】 第一,放事务内的问题:未持有锁时线程阻塞在加锁,但事务已开启、占着一个连接空等;并发上来连接池 maxTotal 被占满,其他请求 getConnection() 超时排队,吞吐骤降甚至雪崩。第二,放事务外且顺序正确:先 tryLock(带等待与租约,如 tryLock(2, 30, SECONDS))→ 开启事务 → 业务写 → 提交 → 在 finally 中 unlock;推荐用 TransactionTemplate 手动控制提交,确保提交完成在解锁前,避免其他线程读到未提交的旧数据。第三,锁粒度与锁内操作:按业务键 lock:order:{orderId} 锁,锁内只做必要写,不做远程调用/慢查询,否则临界区变长、锁超时(watchdog 续期也扛不住远程阻塞)。
【边界】 如果业务可接受短暂读旧或仅做校验,可退化为 DB 乐观锁;若锁内必须远程调用,应预校验在锁外、锁内只写关键路径,或用 MQ 串行化替代。任何方案都要保证解锁在 finally,且提交先于解锁。
题目:使用分布式锁时,分布式锁加在事务外面还是里面,有什么区别?
难度:low
标签:practice/business, distributed/lock, general/knowledge
评分:⭐⭐⭐☆☆(3/5)
使用分布式锁时,分布式锁要放在事务外部,不能放在事务内部,放在事务内,分布式情况下,每个服务都开启了自己的事务,造成多事务的阻塞,把压力都转到了数据库,可能会造成大量阻塞;放在事务外侧,才能有效阻断分布式环境下多端点之间的竞争,减少事务资源浪费;并且在事务内会让事务过程中额外产生远程调用,将事务的范围扩大影响事务执行,并且在事务内如果没有持有到锁导致阻塞等待,会直接阻塞一个数据库链接,最终可能使得数据库无法使用链接全部被占用,一定不要再事务内做锁;
评分:⭐⭐⭐☆☆(3/5)
优点:结论正确锁放事务外、讲出事务内持锁等待阻塞 DB 连接可能耗尽连接池、讲到事务范围扩大。
不足:没讲锁释放时机 vs 事务提交的竞态(提交发生在解锁之后会出现 B 读旧数据)、没提正确姿势(lock→事务→提交→unlock 且解锁放 finally、TransactionTemplate 手动控制)、没讲锁内做远程调用放大锁粒度与死锁风险。
补充:核心结论不变但要补"解锁≠提交完成"的竞态(推荐 TransactionTemplate 手动控制提交在解锁前)。
完整讲解见上方「完整讲解(权威版)」