线程并行处理导致数据错乱-Lost Update 根因分析

发布时间:2026/9/16 10:02:37
线程并行处理导致数据错乱-Lost Update 根因分析 前段时间世界杯活动期间出线了一些问题现在有时间好好整理下用户使用积分投注比赛然后每天定时任务执行如果投注成功则发放一定的积分奖励因为一天可能有多场比赛每天定时任务扫描所有比赛然后每场比赛晒出所有压中用户发放奖励。每场比赛都是一个线程并行处理。然后出现了问题某个用户反映压中了多场比赛但是只发放了一场比赛。于是排查问题发现压根没有调用积分发放服务所以问题出现在扫描比赛和处理用户压中信息的时候。进一步分析代码 for (GuessBallMatchSummary summary : pendingMatches) {FutureVoid future threadPoolFactory.submit(ThreadPoolFactory.WORLDCUP_SETTLE, () - {try {处理某场比赛逻辑return null;} catch (Exception e) {log.error([定时任务] 结算比赛异常, matchId{}, summary.getMatchId(), e);return null;}});futures.add(future);}发现是并行导致的问题因为多个线程并发修改同一个用户的积分余额产生了丢失更新Lost Update问题。场景还原同一用户在多场比赛中均猜中多场比赛**并行结算**并发调用积分服务。 时间线 → Thread-1 (比赛A结算) Thread-2 (比赛B结算) ═══════════════ ═══════════════ addPointsForWinUser( addPointsForWinUser( userId 10001, userId 10001, bizId bet_101, bizId bet_102, points 50 points 30 ) ) │ │ ▼ ▼ 调用 batchChangePoints 调用 batchChangePoints (同一用户、同一积分) (同一用户、同一积分)本质是「读-改-写」三步操作 sql -- 步骤1: 读取当前积分 SELECT points FROM t_stamp_cards WHERE user_id 10001 AND stamp_cards_id 1; -- 步骤2: 内存中计算新余额 newBalance currentBalance request.points; -- 步骤3: 写回新余额 UPDATE t_stamp_cards SET points newBalance WHERE user_id 10001 AND stamp_cards_id 1; 这三步不是原子操作并发执行时产生竞态 Thread-1 (50) Thread-2 (30) DB 中实际值 ───────────── ───────────── ──────────── 100 SELECT → 读到 100 SELECT → 读到 100 100 newBalance 100 50 150 newBalance 100 30 130 UPDATE SET points 150 150 UPDATE SET points 130 130 ← 覆盖!!! 期望结果: 100 50 30 180 实际结果: 130 Thread-1 的 50 被 Thread-2 静默覆盖积分丢失解决方案找到问题后分析处理数据有多少每天最多六场比赛几十万的数据还好改成串行处理

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询