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;
| 操作 | ALGORITHM | LOCK | 说明 |
|---|---|---|---|
| 添加索引 | INPLACE | NONE | ✅ 不锁表 |
| 删除索引 | INPLACE | NONE | ✅ 不锁表 |
| 添加列 | INSTANT(8.0) | NONE | ✅ 秒级完成 |
| 删除列 | INPLACE | NONE | 需重建表 |
| 修改列类型 | COPY | SHARED | ⚠️ 只读锁 |
| 修改主键 | COPY | SHARED | ⚠️ 只读锁 |
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:停写旧列 → 删除旧列
数据库版本管理工具
| 工具 | 语言 | 特点 |
|---|---|---|
| Flyway | Java | SQL 脚本、版本号命名 |
| Liquibase | Java | XML/YAML/SQL、回滚支持 |
| golang-migrate | Go | 轻量、多数据库 |
| Alembic | Python | SQLAlchemy 生态 |
| Prisma Migrate | TypeScript | Prisma ORM 配套 |
| TypeORM Migration | TypeScript | TypeORM 配套 |
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: 如何在不停机的情况下给大表添加列?
答案:
- MySQL 8.0:
ALTER TABLE ... ADD COLUMN ..., ALGORITHM=INSTANT,秒级完成 - MySQL 5.7:
ALTER TABLE ... ADD COLUMN ..., ALGORITHM=INPLACE, LOCK=NONE - 不支持 Online DDL 时:使用 gh-ost 或 pt-online-schema-change
Q2: 为什么不能直接重命名列?
答案:
在滚动部署期间,新旧版本代码并存:
- 直接重命名后,旧版本代码仍读写旧列名 → 报错
- 必须分步:添加新列 → 双写 → 迁移数据 → 切读 → 删旧列
这个过程可能需要多次发版才能完成,也叫 Expand and Contract 模式。
Q3: DB Migration 工具有什么好处?
答案:
- 版本化:每个变更有唯一版本号,可追溯
- 可重复性:在任何环境执行相同的 Migration 得到相同的 Schema
- 团队协作:Migration 文件纳入 Git 管理,代码审核
- 自动执行:CI/CD 中自动应用 Migration
- 回滚支持:部分工具支持 down migration