# BUG: 挖矿分配并发覆盖贡献值同步 (Lost Update) **状态**: 待解决 **严重程度**: 高 - 影响用户挖矿收益 **发现日期**: 2026-03-03 **影响范围**: 所有预种用户 (D2602260xxxx),12个账户数据异常 --- ## 1. 问题描述 用户购买多份预种后,contribution-service 正确记录了累计贡献值,但 mining-service 中只显示第一份的贡献值,导致挖矿收益严重偏低。 **示例:** - D26022600018 购买 4 份预种 → contribution 记录 13,117.860 → mining 只显示 3,279.465 - D26022600001 购买 5 份合成 1 棵树 → contribution 记录 16,397.325 → mining 只显示 3,279.465 --- ## 2. 根因分析 ### 主因:MiningDistributionService 的 save() 覆盖 totalContribution mining-service 存在两个并发写入路径共享同一个 `save()` 方法: **路径 A - 贡献值同步**(Kafka 事件触发,不定时): ``` ContributionEventHandler → ContributionSyncService.handleContributionCalculated() → account.updateContribution(newValue) // 更新 totalContribution → miningAccountRepository.save(account) // upsert 所有字段 ``` **路径 B - 挖矿分配**(每秒执行): ``` MiningDistributionService.executeSecondDistribution() → findAllWithContribution() // 加载账户到内存(含 totalContribution) → account.mine(reward) // 只改 totalMined/availableBalance → miningAccountRepository.save(account) // upsert 所有字段(含 stale totalContribution) ``` **竞争时序:** ``` T0 挖矿分配加载 D26022600018, totalContribution=3279.465 T1 贡献值同步更新 D26022600018 → totalContribution=13117.860 ✓ (DB 已更新) T2 挖矿分配保存 D26022600018, 写回 totalContribution=3279.465 ← 覆盖! ``` **日志证据:** ``` 03/01 02:50:30 - Updated contribution for D26022600001: 16397.325 ← 同步成功 03/03 当前 DB - D26022600001 totalContribution = 3279.465 ← 被覆盖回旧值 ``` 挖矿分配每秒运行,贡献值同步后几乎必然在下一秒被覆盖。 **代码位置:** - `mining-account.repository.ts:57-62` — save() 的 upsert 无条件写入所有字段 - `mining-distribution.service.ts:122-135` — 每秒加载→mine()→save() - `mining-account.aggregate.ts:126-141` — mine() 只改余额但 save() 会写回所有字段 ### 次因:DailySnapshot 全量同步完全失效 本应作为安全网的每日全量同步,自上线以来一直 synced 0 accounts: ``` 03/01 01:00 - DailySnapshotCreated for 2026-02-27 → synced 0 accounts 03/02 01:00 - DailySnapshotCreated for 2026-02-28 → synced 0 accounts 03/03 01:00 - DailySnapshotCreated for 2026-03-01 → synced 0 accounts ``` **原因:** 1. contribution-service 发布的事件 payload 缺少 `snapshotId` 字段 → mining-service 读到 undefined 2. 字段名不匹配:contribution 发 `networkTotalContribution`,mining 读 `totalContribution` 3. `fetchContributionRatios()` 返回数据解析失败(`result.data` 路径不对) **代码位置:** - `contribution-service/snapshot.service.ts:99-110` — payload 缺少 snapshotId - `mining-service/contribution-sync.service.ts:63-68` — 期望 snapshotId - `mining-service/contribution-sync.service.ts:126-141` — API 调用和解析 --- ## 3. 受影响用户 | 用户 | contribution 正确值 | mining 当前值 | 差额 | |------|-------------------|-------------|------| | D26022600001 | 16,397.325 | 3,279.465 | -13,117.860 | | D26022600014 | 13,253.562 | 3,279.465 | -9,974.097 | | D26022600015 | 6,649.398 | 3,279.465 | -3,369.933 | | D26022600016 | 13,117.860 | 3,279.465 | -9,838.395 | | D26022600018 | 13,117.860 | 3,279.465 | -9,838.395 | | D26022600033 | 3,754.422 | 3,279.465 | -474.957 | | D26022600034 | 6,988.653 | 3,279.465 | -3,709.188 | | D26022600035 | 3,799.656 | 3,392.550 | -407.106 | | D26022600036 | 17,211.537 | 3,279.465 | -13,932.072 | | D26022600037 | 16,397.325 | 3,279.465 | -13,117.860 | | D26022600038 | 19,676.790 | 3,279.465 | -16,397.325 | | D26022600043 | 6,558.930 | 3,279.465 | -3,279.465 | D26022600000 是唯一正常的预种用户。旧用户 (D2512xxxx) 不受影响。 --- ## 4. 为什么旧用户不受影响 旧用户在系统稳定后完成了一次性同步,之后没有新的贡献值变更事件。 挖矿分配虽然每秒覆写 totalContribution,但写回的值恰好等于正确值(没有新同步来改变它)。 预种用户是分批多次产生贡献值的(先 1 份 → 2 份 → ... → N 份), 所以挖矿分配总是把后续累加的正确值覆盖回最初同步的旧值。