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 中增加校验:检查所有迁移文件的版本号是否唯一、是否大于当前主干最大版本号。
四、分支策略
数据库迁移脚本的分支策略应与应用代码保持一致,但需额外注意“顺序冲突”。推荐采用 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 |
综合来看,版本控制策略的落地可以归纳为一条可执行的检查链:命名规范保证可读,目录结构保证可找,版本号管理保证有序,分支策略保证并行安全,代码评审保证质量。五者缺一不可,共同构成数据库迁移 CI/CD 的基石。