Skip to content

fix(database): 修复玩家数据并发写入一致性#710

Open
zhibeigg wants to merge 1 commit into
TabooLib:dev/6.3.0from
zhibeigg:fix/703-player-database-consistency
Open

fix(database): 修复玩家数据并发写入一致性#710
zhibeigg wants to merge 1 commit into
TabooLib:dev/6.3.0from
zhibeigg:fix/703-player-database-consistency

Conversation

@zhibeigg

@zhibeigg zhibeigg commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

原有问题

玩家数据库的表约束、首次写入和延迟缓存写回不是一个完整的原子流程:

  • 数据表缺少可靠的 (user, key) 复合唯一约束,并发首次写入采用先查后插时可能同时创建多行。
  • 缓存值、延迟落库时间和调度状态分别更新,旧快照可能在新值之后写入数据库。
  • 升级已有数据库时,同名但字段错误的索引可能被误认为目标唯一索引;已有重复记录也会阻止建索引。
  • 调度器拒绝任务或插件关闭时,缓存状态可能停留在“已安排但实际未执行”。

典型触发场景与后果

  • 两个线程、两个服务器节点或首次登录流程同时写入同一玩家键:产生重复行,后续读取结果不确定。
  • 玩家数据在延迟保存窗口内被连续更新:较早的值可能最后落库,覆盖最新数据,造成回档或丢失更新。
  • 旧版本数据库已经存在重复数据或错误同名索引:迁移失败,或者看似成功但唯一性仍未建立。
  • 插件禁用期间调度延迟保存:任务被拒绝后数据既未落库,也可能无法再次安排。

本 PR 修改

  • 为玩家数据表建立 (user, key) 复合唯一约束;升级时按稳定顺序保留最新记录,再创建正确索引。
  • MySQL 使用原子重复键更新,SQLite 使用兼容旧版本的原子替换写入,避免并发首次写产生重复行。
  • 按键同步缓存值、延迟期限和调度决策,阻止旧值回写、提前落库和重复调度。
  • 校验索引实际列定义,不再只按名称判断;补齐特殊表名与调度拒绝处理。
  • 增加并发初始化、并发写入、重复数据迁移、错误索引、延迟写和调度失败测试。

修改目的

保证同一玩家键在并发创建、连续更新和插件关闭场景下仍只有一个确定的最终值,避免重复记录、数据回档和迁移后约束失效。

兼容性与行为变化

  • 保持现有玩家数据 API。
  • 已有重复记录会按稳定规则归并后建立唯一索引。
  • 同键并发写入改为数据库原子语义,最终结果不再依赖查询与插入的线程时序。

验证

  • ./gradlew :module:database:database-player:build :module:database:database-player-redis:build --rerun-tasks --no-parallel --stacktrace
  • 并发首次写入测试额外连续强制重跑 3 次
  • git diff --check
  • 最终只读复审未发现高、中严重问题

Refs #703

通过复合唯一索引、兼容迁移和原子 upsert 阻止重复记录,并收敛缓存延迟写与调度竞态以确保最新值最终落库。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant