跳到主要内容

Schema 演进与版本管理

问题

如何在不停机的情况下修改数据库表结构?Schema 变更如何做到向后兼容?数据库版本如何管理?

答案

Schema 变更的挑战

生产环境修改表结构面临的问题:

问题说明
锁表ALTER TABLE 可能锁定整个表
耗时大表 DDL 可能执行数十分钟
兼容性新旧代码并存时 Schema 必须兼容
回滚Schema 变更回滚成本高

Online DDL

MySQL 5.6+ 支持 Online DDL,大部分 DDL 操作不阻塞 DML:

-- 指定 ALGORITHM 和 LOCK
ALTER TABLE orders
ADD INDEX idx_status(status),
ALGORITHM=INPLACE,
LOCK=NONE;
操作ALGORITHMLOCK说明
添加索引INPLACENONE✅ 不锁表
删除索引INPLACENONE✅ 不锁表
添加列INSTANT(8.0)NONE✅ 秒级完成
删除列INPLACENONE需重建表
修改列类型COPYSHARED⚠️ 只读锁
修改主键COPYSHARED⚠️ 只读锁
MySQL 8.0 Instant DDL

MySQL 8.0 支持 ALGORITHM=INSTANT,添加列操作秒级完成(只修改元数据,不重建表):

ALTER TABLE orders ADD COLUMN remark VARCHAR(500), ALGORITHM=INSTANT;

限制:只能在表末尾添加列(8.0.29 开始支持任意位置)。

工具辅助 DDL

对于不支持 Online DDL 的操作,使用专业工具:

工具原理优势
pt-osc创建影子表 + 触发器同步 + 交换表名Percona 出品,久经验证
gh-ost创建影子表 + binlog 同步 + 交换表名GitHub 出品,不用触发器
# gh-ost 示例
gh-ost \
--host=master \
--database=mydb \
--table=orders \
--alter="ADD COLUMN shipping_fee DECIMAL(10,2) DEFAULT 0" \
--execute

Schema 变更的向后兼容原则

在滚动部署(新旧版本并存)场景下,Schema 变更必须向后兼容:

安全的变更(可直接执行)

操作为什么安全
添加可选列(有默认值)旧代码不知道新列,不受影响
添加新表旧代码不访问新表
添加索引不影响数据,只影响查询计划

危险的变更(需要分步执行)

删除列

步骤 1:新版本代码不再读写该列(但列还在)
步骤 2:全量部署新版本后,再 ALTER TABLE 删除列

重命名列

步骤 1:添加新列 → 同时写新旧两列 → 迁移数据
步骤 2:所有代码改为读新列
步骤 3:停止写旧列
步骤 4:删除旧列

修改列类型

步骤 1:添加新类型的新列
步骤 2:双写(新旧列都写)
步骤 3:迁移历史数据
步骤 4:切换代码读新列
步骤 5:停写旧列 → 删除旧列

数据库版本管理工具

工具语言特点
FlywayJavaSQL 脚本、版本号命名
LiquibaseJavaXML/YAML/SQL、回滚支持
golang-migrateGo轻量、多数据库
AlembicPythonSQLAlchemy 生态
Prisma MigrateTypeScriptPrisma ORM 配套
TypeORM MigrationTypeScriptTypeORM 配套

Flyway 示例

db/migration/
├── V1__create_users.sql
├── V2__create_orders.sql
├── V3__add_phone_to_users.sql
└── V4__add_index_on_orders.sql
-- V3__add_phone_to_users.sql
ALTER TABLE users ADD COLUMN phone VARCHAR(20) DEFAULT NULL;
CREATE INDEX idx_phone ON users(phone);

变更审核清单

每次 Schema 变更前需检查:

检查项说明
✅ 是否需要 Online DDL大表必须用 Online DDL 或 gh-ost
✅ 是否向后兼容新旧代码并存时是否正常
✅ 是否有回滚方案变更出错如何回退
✅ 是否通知相关方DBA、上下游团队
✅ 是否在低峰期执行避免高峰时段
✅ 是否影响索引新列是否需要索引

常见面试问题

Q1: 如何在不停机的情况下给大表添加列?

答案

  1. MySQL 8.0ALTER TABLE ... ADD COLUMN ..., ALGORITHM=INSTANT,秒级完成
  2. MySQL 5.7ALTER TABLE ... ADD COLUMN ..., ALGORITHM=INPLACE, LOCK=NONE
  3. 不支持 Online DDL 时:使用 gh-ost 或 pt-online-schema-change

Q2: 为什么不能直接重命名列?

答案

在滚动部署期间,新旧版本代码并存:

  • 直接重命名后,旧版本代码仍读写旧列名 → 报错
  • 必须分步:添加新列 → 双写 → 迁移数据 → 切读 → 删旧列

这个过程可能需要多次发版才能完成,也叫 Expand and Contract 模式。

Q3: DB Migration 工具有什么好处?

答案

  1. 版本化:每个变更有唯一版本号,可追溯
  2. 可重复性:在任何环境执行相同的 Migration 得到相同的 Schema
  3. 团队协作:Migration 文件纳入 Git 管理,代码审核
  4. 自动执行:CI/CD 中自动应用 Migration
  5. 回滚支持:部分工具支持 down migration

相关链接