分布式锁与@transactional组合失效的根本原因是锁获取晚于事务开启,导致多个线程并发进入事务读取旧数据,引发超卖;正确顺序必须是加锁→执行事务→释放锁,且锁粒度需与事务资源精确对齐。

Java 中事务管理与分布式锁的结合,关键在于锁的获取时机必须早于数据库事务开启,且锁的生命周期需覆盖整个事务执行过程。否则会出现“先开事务再抢锁”导致的并发冲突,或“事务已提交但锁提前释放”引发的数据不一致。
为什么不能在 @Transactional 内部加锁
Spring 的 @Transactional 是基于 AOP 的代理机制,在方法入口开启事务、出口提交或回滚。如果在事务方法内部手动加锁(比如调用 Redisson.lock()),此时事务已启动,但锁尚未生效——多个线程可能同时进入事务,读取相同数据(如库存=100),各自扣减后都写入数据库,造成超卖。
正确顺序只能是:加锁 → 执行事务方法 → 释放锁,且加锁失败应直接拒绝请求,不进入事务。
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
推荐的结合方式:AOP + 自定义注解
通过切面在事务执行前统一加锁,确保互斥性前置。典型结构如下:
- 定义 @DistributedLock 注解,支持 SpEL 表达式解析参数(如 keys = {"#userId", "#skuId"})
- AOP 切面拦截该注解,在 proceed() 前调用 Redisson.tryLock(),失败抛异常中断流程
- @Transactional 标注在被拦截的方法上,事务在锁持有期间执行
- finally 块中安全释放锁(仅当前线程持有时才 unlock)
锁粒度与事务边界要对齐
锁的 Key 必须精确反映事务操作的资源范围,避免过粗或过细:
- 下单场景:用
"order:#{#userId}"锁用户维度,防止同一用户重复提交;若需防库存超卖,则应为"stock:#{#skuId}" - 配置更新:用
"config:#{#env}"锁环境维度,避免多节点同时刷缓存 - 切忌全局锁(如 "global_lock"),会严重降低并发能力
注意 Redis 锁与数据库事务的语义差异
Redis 分布式锁不提供 ACID,它只保证“同一时刻最多一个客户端执行”,而数据库事务保证“执行过程中的原子性与一致性”。两者是互补关系:
- 锁解决的是“谁有资格执行事务”的问题
- 事务解决的是“执行过程中数据是否可靠落地”的问题
- 例如:扣库存 + 发消息(Kafka)不属于同一数据库事务,此时需用分布式锁协调跨系统操作,再配合本地事务 + 最终一致性方案
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










