跳转到内容

数据库迁移与结构升级规范

DDL 变更、版本化、回滚(Flyway / Liquibase / Alembic)


【Standard】必须使用版本化迁移工具管理 DDL,禁止手动直接在生产库执行 DDL。

工具适用场景配置文件
FlywayJava/Spring 项目,SQL 优先flyway.conf
Liquibase多数据库兼容,支持 XML/YAML 变更集changelog.xml / changelog.yaml
AlembicPython 项目,SQLAlchemy 生态alembic.ini + env.py
golang-migrateGo 项目migrations/ 目录
src/main/resources/
└── db/
└── migration/
├── V1.0.0__init_schema.sql
├── V1.0.1__add_user_phone.sql
├── V1.0.2__create_order_table.sql
└── V1.1.0__add_order_index.sql
V<版本号>__<描述>.sql
  • V:版本迁移(Versioned),前缀大写 V
  • 版本号:主.次.修订,如 1.0.0、1.0.1,严格递增
  • 双下划线 __:分隔版本号与描述
  • 描述:蛇形命名,简洁说明变更内容

【Requirement】版本号全局唯一、严格递增;已发布的迁移脚本禁止修改,只能新增版本。

  • 采用语义化版本 MAJOR.MINOR.PATCH
  • 同一版本内的变更可拆分为多个脚本:V1.0.0__01_create_user.sql、V1.0.0__02_create_order.sql
  • PATCH 版本用于小变更(加字段、加索引),MINOR 用于新增表/功能,MAJOR 用于破坏性变更
  • 已合并到主干并执行过的脚本,禁止修改内容,必须通过新版本修复
  • Flyway 会对每个脚本计算 checksum 存入 flyway_schema_history 表
  • 修改已执行脚本会导致 checksum 不匹配,启动失败
  • 修复方式:使用 flyway repair 或新增脚本修正

示例:

-- V1.0.0__init_schema.sql
CREATE TABLE sys_user (
  id BIGINT PRIMARY KEY,
  username VARCHAR(64) NOT NULL
);
 
-- V1.0.1__add_user_phone.sql
ALTER TABLE sys_user ADD COLUMN phone VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手机号' AFTER username;

反模式:

-- 错误:直接修改已发布的 V1.0.0 脚本
-- V1.0.0__init_schema.sql(被修改)
CREATE TABLE sys_user (
  id BIGINT PRIMARY KEY,
  username VARCHAR(64) NOT NULL,
  phone VARCHAR(20) NOT NULL DEFAULT ''  -- 不应在此修改
);

【Requirement】DDL 必须可重复执行(幂等)、可逆;大表变更必须使用在线 DDL 工具(pt-online-schema-change / gh-ost)。

使用 IF NOT EXISTS / IF EXISTS 保证重复执行不报错:

-- 建表
CREATE TABLE IF NOT EXISTS order_order ( ... );
 
-- 加字段
ALTER TABLE order_order ADD COLUMN IF NOT EXISTS remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注';
 
-- 加索引
ALTER TABLE order_order ADD INDEX IF NOT EXISTS idx_user_id (user_id);
 
-- 删字段
ALTER TABLE order_order DROP COLUMN IF EXISTS remark;

【Caution】禁止直接 ALTER TABLE 修改大表结构,会锁表导致业务不可用。

必须使用在线 DDL 工具:

# pt-online-schema-change 示例
pt-online-schema-change \
  --alter "ADD COLUMN phone VARCHAR(20) NOT NULL DEFAULT '' AFTER username" \
  D=mydb,t=sys_user \
  --execute
 
# gh-ost 示例
gh-ost \
  --alter="ADD COLUMN phone VARCHAR(20) NOT NULL DEFAULT '' AFTER username" \
  --database=mydb --table=sys_user \
  --execute
  • 新增字段必须有默认值,避免 NULL 导致查询异常
  • 大表加字段使用 ALGORITHM=INPLACE, LOCK=NONE(MySQL 5.6+)
  • 优先在表末尾追加字段,避免中间插入(AFTER)导致表重建
-- 正确:末尾追加 + 默认值
ALTER TABLE order_order ADD COLUMN remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注';
 
-- 谨慎:中间插入(大表慎用)
ALTER TABLE order_order ADD COLUMN remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注' AFTER order_no,
  ALGORITHM=INPLACE, LOCK=NONE;

【Caution】删除字段前必须确认无代码引用,分两步走:先标记废弃,下个版本再删除。

-- 第一步:字段标记废弃(注释更新,保留字段)
ALTER TABLE order_order MODIFY COLUMN old_field VARCHAR(100) COMMENT '已废弃';
 
-- 第二步:下个版本真正删除
ALTER TABLE order_order DROP COLUMN old_field;

【Standard】每个破坏性变更必须提供回滚脚本(Undo),版本号为 U<版本号>。

变更类型可回滚回滚脚本
加字段✅DROP COLUMN
加索引✅DROP INDEX
加表✅DROP TABLE
改字段类型⚠️ 部分需数据兼容
删字段❌需备份数据
删表❌需备份数据
-- V1.0.1__add_user_phone.sql(正向)
ALTER TABLE sys_user ADD COLUMN phone VARCHAR(20) NOT NULL DEFAULT '' COMMENT '手机号';
 
-- U1.0.1__drop_user_phone.sql(回滚)
ALTER TABLE sys_user DROP COLUMN phone;
# Flyway 回滚(需 Team 版)
flyway undo
 
# 手动回滚:执行对应的 U 脚本
mysql -u root -p mydb < U1.0.1__drop_user_phone.sql
  • 任何 DROP / TRUNCATE 操作前,必须先执行数据备份
  • 删除字段前,执行 SELECT COUNT(*) FROM table WHERE old_field IS NOT NULL 确认数据可废弃
  • 回滚脚本与正向脚本必须在同一 PR 中提交

【Requirement】DDL 变更必须经过:本地验证 → 测试环境 → 预发环境 → 生产环境,四级灰度。

1. 开发本地执行迁移脚本,验证 DDL 正确性
2. 提交 PR,包含正向脚本 + 回滚脚本
3. CI 自动在测试库执行迁移,通过后合并
4. 测试环境验证功能
5. 预发环境执行迁移 + 回归测试
6. 生产环境:低峰期执行,监控慢查询与锁
7. 执行后检查 flyway_schema_history 表确认成功
  • 执行时间:业务低峰期(凌晨 2:00 - 5:00)
  • 执行前:全量备份数据库
  • 执行方式:通过迁移工具执行,禁止手动 mysql < script.sql
  • 执行后:flyway info 检查状态,确认所有脚本 Success
  • 监控:执行期间开启慢查询日志,监控连接数与锁等待
失败场景处理方式
脚本语法错误修复脚本,使用 flyway repair 更新 checksum
执行超时锁表KILL 进程,检查是否有残留,执行回滚脚本
数据转换失败暂停发布,恢复备份,分析失败原因
部分脚本成功成功的脚本不回滚,修复失败脚本后继续执行

示例(Flyway 配置):

# flyway.conf
flyway.url=jdbc:mysql://localhost:3306/mydb
flyway.user=root
flyway.password=secret
flyway.locations=filesystem:src/main/resources/db/migration
flyway.baseline-on-migrate=true
flyway.validate-on-migrate=true
flyway.clean-disabled=true