2. 版本存储机制

配方版本管理,说白了就是解决「怎么存」的问题。你想想看,一个配方可能被修改几十次甚至上百次,每次改了什么、谁改的、什么时候改的,这些信息如果存得不好,后面做对比和回滚就会非常痛苦。我在好几个项目里都踩过这方面的坑,今天就把这些经验掰开揉碎讲给你听。

版本表设计:核心就是三张表

我个人习惯把版本相关的表拆成三张:配方主表、版本记录表、版本内容表。为什么拆三张?因为职责要分离。

表名 核心字段 作用
recipe recipe_id, name, status, current_version 记录配方本身的基本信息,不存历史
recipe_version version_id, recipe_id, version_no, created_by, created_at, comment 记录每次版本的元数据,谁在什么时候做了什么
recipe_version_content version_id, content (JSON/XML), checksum 存储版本的实际内容,可以是全量快照或增量
我的经验:version_no 不要用自增 ID,建议用「主版本.次版本.修订号」的格式,比如 2.1.3。这样一看就知道是大改还是小修。

快照存储策略:简单但占空间

快照策略,就是每次保存版本时,把整个配方内容完整存一份。我在早期的一个项目中就是这么干的,实现起来确实简单——

-- 保存新版本时,直接把当前完整内容写入
INSERT INTO recipe_version_content (version_id, content, checksum)
VALUES (1024, '{"materials": [...], "steps": [...], "params": {...}}', 'sha256:abc123...');

优点很明显:读取快,回滚时直接拿那个版本的内容就行,不需要做任何计算。但缺点也致命——存储空间会随着版本数量线性增长。一个配方如果改了 100 次,就要存 100 份完整内容。

注意:如果配方内容很大(比如包含工艺图纸的 Base64 编码),快照策略会让数据库迅速膨胀。我曾经见过一个系统,半年就撑爆了 500GB 的磁盘。

增量存储策略:省空间但费计算

增量策略只存每次变更的差异部分。常见的做法是用 diff 算法 或者 操作日志。

-- 增量存储示例:只记录变更的字段
INSERT INTO recipe_version_content (version_id, content, change_type)
VALUES (1025, '{"changed_fields": {"temperature": 180}}', 'partial_update');

这样做的好处是省空间,但坏处也明显——要拿到某个历史版本的完整内容,得从初始版本开始,把之后所有的增量一个个应用上去。版本链越长,重建速度越慢。

我后来在项目中用了混合策略:每 5 个版本做一次全量快照,中间的版本只存增量。这样既控制了存储成本,又保证了重建速度不会太慢。

策略 存储成本 读取速度 回滚复杂度
全量快照 高 快(直接读取) 低
纯增量 低 慢(需重建) 高
混合(推荐) 中 中(最多重建 5 步) 中

版本元数据管理:别小看这些「附属信息」

很多人只关注内容怎么存,却忽略了元数据。其实元数据才是版本管理的灵魂。我习惯在每个版本记录里至少包含以下信息——

  • 版本号:语义化版本,比如 1.2.0
  • 创建人:谁做的修改
  • 创建时间:精确到毫秒
  • 变更说明:为什么改,改了哪里
  • 父版本 ID:指向上一个版本,形成版本链
  • 标签:比如「已审核」、「已发布」、「测试中」
  • 校验和:内容的 SHA256,防止数据篡改

核心原则:元数据要足够描述「谁、在什么时候、为什么、做了什么改变」。这样后面做审计和追溯时,你才不会抓瞎。

版本存储的核心流程

下面这张图展示了从创建版本到存储的完整流程,我把它画成了 SVG,方便你理解——

版本存储核心流程 版本存储核心流程 用户提交修改 生成版本号 判断存储策略(快照/增量/混合) 全量快照存储 增量差异存储 写入版本表 + 内容表 说明:混合策略每 N 次全量快照后,中间版本只存增量,平衡存储与读取性能 元数据(版本号、创建人、标签等)在写入版本表时一并记录

避坑指南:我踩过的几个坑

  • 不要用时间戳做版本号:同一秒内可能产生多个版本,时间戳会重复。我吃过这个亏,后来全改成语义化版本了。
  • 校验和一定要存:有一次数据库磁盘坏了,部分数据被篡改,幸好有校验和才发现问题。没有校验和,你根本不知道数据对不对。
  • 版本链不要断:删除版本时要小心,如果删了中间某个版本,后面的增量就重建不出来了。我建议用软删除,标记为「已废弃」而不是物理删除。
  • 大内容要压缩:如果配方内容包含大量文本或 Base64 图片,存之前用 gzip 压缩一下,能省 60%-80% 的空间。
一个小技巧:在 recipe_version 表上加一个 tag 字段,用来标记「基线版本」。回滚时优先选择基线版本,因为它们是经过验证的稳定版本。