2. 数据库迁移的版本控制策略

数据库迁移脚本一旦进入团队协作,就不再是“某个人电脑上的一次性 SQL”,而是一份需要长期维护、可追溯、可回滚的代码资产。版本控制策略要解决的核心问题是:让每一次结构变更都有唯一身份、明确顺序和可审计的评审记录。本节围绕命名规范、目录结构、版本号管理、分支策略与代码评审五个方面展开,并给出可直接落地的示例。

一、迁移脚本的命名规范

命名规范的目标是让文件名本身携带足够信息,使人在不打开文件的情况下就能判断它的作用、顺序和影响范围。推荐采用如下格式:

V{版本号}__{描述}__{作者}.sql
示例:
V20240601120000__create_users_table__zhangsan.sql
V20240601120500__add_email_index_to_users__lisi.sql
V20240601121000__drop_legacy_order_flag__wangwu.sql

其中版本号使用时间戳(UTC,精确到秒)而非自增整数,原因是多人并行开发时自增整数极易冲突,而时间戳天然单调递增且全局唯一。描述部分使用小写字母加下划线,动词开头,明确表达“创建、新增、删除、修改”等意图。作者字段便于追责和沟通。对于回滚脚本,可采用 U{版本号}__{描述}.sql 或 R{版本号}__{描述}.sql 的对称命名,与正向脚本成对存放。

二、目录结构设计

目录结构应体现“迁移脚本按时间线性排列、按环境隔离配置、按模块可分组”的原则。一个典型的项目结构如下:

db/
├── migrations/
│   ├── V20240601120000__create_users_table.sql
│   ├── V20240601120500__add_email_index_to_users.sql
│   └── V20240601121000__drop_legacy_order_flag.sql
├── rollback/
│   ├── U20240601120000__create_users_table.sql
│   └── U20240601120500__add_email_index_to_users.sql
├── seeds/
│   └── V90000000000000__seed_roles.sql
└── config/
    ├── dev.yaml
    ├── staging.yaml
    └── prod.yaml

migrations 目录只存放正向变更,rollback 目录存放对应回滚脚本,seeds 用于初始化基础数据(版本号用 90000000000000 这类高位前缀,确保在业务迁移之后执行)。config 目录按环境存放连接串、schema 名等参数,避免硬编码。所有脚本必须纳入 Git 管理,禁止在数据库客户端手工执行后不落盘。

三、版本号管理

版本号管理的关键是“唯一、有序、不可变”。一旦某个版本号被合并到主干并部署到任意环境,就不允许再修改该脚本内容,否则不同环境会出现同一版本号对应不同结构的情况。若发现已合并脚本有误,正确做法是新增一个修复版本,而不是修改历史。

版本号与数据库中的迁移记录表(如 schema_version)一一对应。每次执行迁移时,工具会先查询该表,跳过已应用的版本,只执行未应用的脚本。因此版本号必须严格递增,且同一版本号只能出现一次。对于并行开发,建议在 CI 中增加校验:检查所有迁移文件的版本号是否唯一、是否大于当前主干最大版本号。

迁移脚本版本控制与执行链路 feature 分支 新增 V20240601… main 主干 合并 + 版本号校验 schema_version 已应用版本记录 迁移执行顺序与状态判断 读取 migrations 按版本号排序 查询 schema_version 比对已应用集合 未应用? 是 → 执行 否 → 跳过 记录日志 版本号唯一且递增 · 已合并脚本不可修改 · 回滚脚本与正向脚本成对维护

四、分支策略

数据库迁移脚本的分支策略应与应用代码保持一致,但需额外注意“顺序冲突”。推荐采用 Git Flow 的简化版:

  • main 分支:只接受通过评审和 CI 的合并,代表可部署到生产的状态。每次合并后自动触发迁移校验。
  • feature 分支:每个功能或修复从 main 切出,迁移脚本在 feature 分支内新增。合并前必须 rebase 到最新 main,确保版本号大于主干当前最大版本。
  • release 分支:用于预发布环境验证,迁移脚本在此分支冻结,不再新增,只允许修复。

关键规则:禁止在 feature 分支中修改已合并到 main 的迁移脚本。如果两个 feature 分支都新增了迁移,合并时后合并者必须检查版本号是否冲突,若冲突则重命名自己的脚本版本号并重新排序。CI 中可加入脚本:git diff --name-only origin/main...HEAD -- db/migrations/,检查新增文件版本号是否大于 main 中最大值。

五、代码评审要点

迁移脚本的评审不同于普通业务代码,应重点关注以下方面:

评审项检查内容常见问题
命名与版本号是否符合 V{时间戳}__{描述}__{作者} 格式,版本号是否唯一且递增使用自增整数导致冲突;描述含糊如 “update”
幂等性是否使用 IF NOT EXISTS / IF EXISTS,重复执行是否安全直接 CREATE TABLE 导致二次执行报错
回滚脚本是否提供对应 U 脚本,回滚逻辑是否可逆只写正向脚本,无法回退
锁与性能大表加索引是否使用 CONCURRENTLY,是否避免全表锁在千万级表上直接 ALTER TABLE 加列
数据安全是否包含 DROP/TRUNCATE,是否有备份或双写方案直接 DROP COLUMN 导致数据丢失
环境兼容是否使用了特定数据库版本语法,是否兼容目标环境开发用 MySQL 8,生产是 MySQL 5.7
评审时建议要求作者在 PR 描述中附上:迁移目的、影响表、预估执行时长、回滚方案、是否需要在低峰期执行。对于涉及大表结构变更的脚本,应先在 staging 环境用生产数据量级验证执行时间。
严禁在迁移脚本中混入业务数据修改(如批量 UPDATE 用户余额)。数据修复应通过独立的数据迁移脚本或运维工单处理,避免结构变更与数据变更耦合导致回滚困难。

综合来看,版本控制策略的落地可以归纳为一条可执行的检查链:命名规范保证可读,目录结构保证可找,版本号管理保证有序,分支策略保证并行安全,代码评审保证质量。五者缺一不可,共同构成数据库迁移 CI/CD 的基石。