分布式锁 + 事务防重失效 —— 警惕事务粒度

分布式锁 + 事务防重失效 ------ 警惕事务粒度

最近接入了一个二方流程引擎,在生产环境中发现了审批任务重复提交成功 的问题,导致业务数据被重复写入。这个问题不仅会造成数据混乱,严重时甚至可能引发重复账务处理,后果不容忽视。

背景场景

用户在前端点击"审批通过"后,后台会提交任务并写入业务表。由于提交流程中存在耗时操作 ,用户在等待过程中多次点击按钮,结果多次提交均成功返回,后台也落了多条数据

初步以为是并发未加锁。但经排查,系统使用了 Lock4j(基于 Redisson)的分布式锁机制。任务提交方法上的处理包括:

  • 使用 @Lock4j 注解;
  • 使用 @Transactional(Propagation.REQUIRED) 注解;
  • 锁获取超时 10 秒,锁持有时间 60 秒;
  • 正常情况下,第一次提交后任务会被删除,后续提交应提示"任务不存在"。

看似逻辑完备,但仍发生了重复提交。进一步分析发现了核心原因。

问题根源:锁与事务范围不一致

submitTask 方法同时使用了分布式锁和 REQUIRED 事务注解,意味着它与外部接口共享一个大事务。

问题在于:锁在 submitTask 方法执行结束后即释放,而此时外部事务仍未提交

因此,形成如下并发场景:

  1. 第一个线程获取锁,执行 submitTask,开始事务。
  2. 方法执行结束后释放锁,但事务未提交;
  3. 第二个线程获取锁后进入 submitTask
  4. 由于前一个事务未提交,任务数据仍可查询;
  5. 后续的 updatedelete操作实际未生效(影响行数为 0),但以成功退出;
  6. 最终造成数据重复。

图示:锁释放早于事务提交

sequenceDiagram participant 用户 participant 用户 participant 应用服务 participant 数据库 用户->>应用服务: 点击"审批通过" activate 应用服务 应用服务->>应用服务: 获取分布式锁 应用服务->>数据库: 查询任务 应用服务->>数据库: 修改状态 / 删除 应用服务-->>应用服务: 释放锁(事务未提交) 用户->>应用服务: 重复点击 activate 应用服务 应用服务->>应用服务: 获取锁 应用服务->>数据库: 查询任务 (数据未提交仍存在) 应用服务->>数据库: 操作无效 (行数 = 0) 应用服务-->>用户2: 返回成功 应用服务->>数据库: 提交事务

解决方案:确保范围一致

方案一:扩大锁范围(不推荐)

将分布式锁拉到外部大事务范围,确保整个流程执行期间关键操作不被打断。

  • 优点:逻辑简单
  • 缺点:耗时操作会使锁持久不释,性能降低

方案二:简化事务,缩短耗时(治标不治本)

  • 将耗时操作移出事务范围,或改为异步
  • 有助于缩短锁+事务的同时经历时间

方案三:基于数据库的应急处理(建议)

通过数据库原子操作确保任务不可重复处理,是最稳定方案。

1. SELECT ... FOR UPDATE

查询任务时使用悲观锁,确保同一时间只有一个事务操作该行

sql 复制代码
SELECT * FROM task_table WHERE task_id = ? FOR UPDATE;
2. 判断状态 + 影响行数
sql 复制代码
UPDATE task_table SET status = 'approved' WHERE task_id = ? AND status = 'pending';

如果 affectedRows != 1,则提示任务已处理,阻止重复操作。

总结

锁控并发,事务保数据一致,两者范围不一致时最易出问题

建议:

  • 不依赖分布式锁本身保障应急性
  • 任何不可重复操作,必须在数据库层有备案
  • 状态判断 + 影响行数检查是成本最低且最稳定的方案
  • 有条件时考虑用唯一索引或幂等记录表进一步防重
相关推荐
石榴树下的七彩鱼1 小时前
图片修复 API 接入实战:网站如何自动去除图片水印(Python / PHP / C# 示例)
图像处理·后端·python·c#·php·api·图片去水印
我叫黑大帅1 小时前
为什么TCP是三次握手?
后端·网络协议·面试
我叫黑大帅1 小时前
如何排查 MySQL 慢查询
后端·sql·面试
techdashen1 小时前
Rust项目公开征测:Cargo 构建目录新布局方案
开发语言·后端·rust
消失的旧时光-19431 小时前
Spring Boot 实战(五):接口工程化升级(统一返回 + 异常处理 + 错误码体系 + 异常流转机制)
java·spring boot·后端·解耦
Rust研习社2 小时前
Rust 智能指针 Cell 与 RefCell 的内部可变性
开发语言·后端·rust
夕颜1112 小时前
Skill 机器人 vs Hermes Agent:两种「AI 越用越聪明」的路径
后端
IT_陈寒4 小时前
SpringBoot自动配置把我都整不会了
前端·人工智能·后端
覆东流4 小时前
第1天:Python环境搭建 & 第一个程序
开发语言·后端·python
码事漫谈4 小时前
Token成本失控?两大开源方案如何重构AI编程成本结构
后端