事务与分布式锁是分层协作关系:事务保障单次操作原子性,分布式锁实现多实例串行化;高并发下必须配合使用,前者确保数据库写入一致性,后者防止跨实例竞态条件。

事务和分布式锁不是替代关系,而是分层协作关系:事务管“单次操作的原子性”,分布式锁管“多实例间操作的串行化”。在高并发数据同步场景中,二者必须配合使用,否则容易出现竞态条件或数据不一致。
事务负责数据库层面的原子写入
当一个业务逻辑涉及多个表(比如创建队伍 + 插入用户关系),必须用 @Transactional 保证这些 SQL 要么全成功、要么全回滚。例如:
- 插入队伍记录到
team表 - 插入关联记录到
user_team表 - 若第二步失败,第一步自动回滚,避免“有队伍无成员”的脏数据
但事务只在单个数据库连接内生效,无法阻止其他服务实例同时执行相同逻辑——这就需要分布式锁来兜底。
分布式锁控制跨实例的并发入口
在事务执行前加锁,把高并发请求“排队”成串行调用。以“用户加入队伍”为例:
- 先用 Redisson 获取
RLock lock = redisson.getLock("join_team:" + teamId) - 获取成功后,再进入
@Transactional方法体 - 此时即使 100 个请求同时到达,也只有一个能拿到锁并执行事务,其余等待或失败
这样就规避了“检查人数→判断可加入→插入关系”这一连串操作中的竞态窗口(比如两个请求同时查到人数为 4,都以为还能加 1 人)。
锁粒度与事务边界要对齐
锁的 key 和事务作用范围必须匹配,否则会漏锁或过度锁:
- 对单个队伍的操作,锁 key 应为
"join_team:123",而不是笼统的"join_team" - 锁要在事务开始前获取,且在事务提交/回滚后才释放(Redisson 的
lock()默认支持自动续期+异常安全释放) - 不能在事务内部加锁,否则可能造成死锁或锁持有时间远超必要
避免常见误用
有些做法看似合理,实则埋雷:
- 只用事务不用锁 → 多实例下仍会超限或重复插入
- 只用锁不用事务 → 锁内发生异常时,数据库可能已写入部分数据,无法回滚
- 锁 key 设计不合理(如固定值)→ 所有队伍共用一把锁,吞吐量骤降
- 锁超时时间短于事务最大耗时 → 锁提前释放,其他线程闯入,破坏一致性
真正可靠的高并发数据同步,是让分布式锁做“守门员”,事务做“执行官”——前者决定谁可进门,后者确保进门后动作干净利落。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











