上篇讨论了哪些数据需要存。这篇接着看写入次数:确定要保存的数据,是否每次变化都需要写入 DB?
如果只需要保留最新状态,多次修改就有机会合并为一次写入。 下面以玩家的任务进度为例,看看怎样通过标脏和定时保存减少重复落库,以及保存间隔该如何确定。
多次修改,可以合并为一次写入
假设 DB 中保存的任务进度是 10。两次定时保存之间,进度在内存中依次增加到 11、12、13。
每次变化先更新内存中的进度,并将这份数据标脏,表示它需要保存。后续修改继续更新内存,不必立即发起 DB 写入。
等到定时保存时,进度依次变为 11、12、13 的这几次修改,可以合并为一次落库,只写入最新进度 13。
每次变化都更新业务状态,保存时只写入最新结果。 这样,同一份数据在一个保存周期内的多次修改,就可以合并为一次写入。

任务进度在内存中依次变为 11、12、13,并标记为待保存;定时保存时,只将最新进度 13 写入 DB。
不过,能够合并状态,不代表可以省略业务过程。
任务进度达到条件时,奖励仍然要正确发放。玩家先获得 100 金币,再消耗 80 金币,余额净增加 20,也不能代替业务需要的收支和发奖记录。余额可以在有可靠恢复依据的前提下合并更新,必要记录仍需保留。
定时检查,有修改才保存
检查周期可以按业务能接受的回退和 DB 写入压力确定。 例如每 30 秒检查一次,30 秒仅作示例。间隔越长,通常越有机会合并更多修改,但未落库的变化也可能积累更多。
到期时先检查脏数据:有待保存的修改,就进入保存流程;上次保存已完成、也没有新的修改,就跳过这一轮。同一份数据多次标脏,也不必逐次提交。上轮尚未保存成功的修改仍需保留,不能因为本轮没有再次标脏就跳过。
排队、写入和重试还会拉长未落库时间,因此检查周期不等于回退上限。付费、发奖等要求不能回退的结果,需要及时落库,或先留下可靠的恢复记录。
同一份数据,统一保存
保存逻辑按业务模块组织,由各模块管理对应的数据。数据变化时先更新内存并标脏。
同一份数据共用一个保存入口。 定时保存、下线保存等场景都通过这个入口,统一检查脏标记、提取待保存数据,并带上预期版本号写入 DB。
保存结果也在同一处处理。失败时保留待保存状态,按策略决定是否重试;保存期间产生的新修改,则留待下次保存。

业务先更新内存并标脏,定时或按需保存时,由统一的保存逻辑读取数据并落库。
写入版本与脏状态分别处理
写入时带上预期版本号,只有 DB 中当前版本号与它一致,这次更新才会落地。
本地另记修改版本:提交时记下版本,成功后与当前版本比较。一致才清除脏标记;不一致则保留,留待下次保存。

DB 校验写入版本;保存期间的新修改仍待保存。图中数字为 DB 版本示意。
保存失败时,可以按策略重试;版本不匹配时,立即记录告警日志并触发通知,由负责该数据的存储逻辑协调处理。
回到标题的问题:任务进度变了十次,如果都发生在同一个保存周期内,而且只需要保留最终进度,就可以合成一次写入。周期设多长,要结合业务能接受的回退和实际写入压力确定;没有新的脏数据,也没有遗留的保存需求,就不必为了定时而再写一次。业务发生了多少次变化,不必对应多少次 DB 写入。