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

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

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

背景场景

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

初步以为是并发未加锁。但经排查,系统使用了 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 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
胡写代码1 天前
别再前后端各写一套表单校验了
java·后端
大勇前进1 天前
原生 PHP 还是 Laravel?小项目到底要不要上框架
后端
yuzhi_liu1 天前
我用 LangGraph4j 实现 Multi-Agent Supervisor
后端
alsmile1 天前
Node-RED 之外,国产规则引擎的新方案:基于标准语法,Go 先行实现
后端·开源·go
大白801 天前
PHP 内存溢出排查思路:看懂报错日志,精准定位问题
后端
二月龙1 天前
PHP 接口返回统一响应封装,让前后端对接更省心
后端
盖伦发发1 天前
软件工程SOLID 五大设计原则
后端·软件工程