数据库迁移与结构升级规范
DDL 变更、版本化、回滚(Flyway / Liquibase / Alembic)
【Standard】必须使用版本化迁移工具管理 DDL,禁止手动直接在生产库执行 DDL。
| 工具 | 适用场景 | 配置文件 |
|---|---|---|
| Flyway | Java/Spring 项目,SQL 优先 | flyway.conf |
| Liquibase | 多数据库兼容,支持 XML/YAML 变更集 | changelog.xml / changelog.yaml |
| Alembic | Python 项目,SQLAlchemy 生态 | alembic.ini + env.py |
| golang-migrate | Go 项目 | 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.sqlV<版本号>__<描述>.sqlV:版本迁移(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