数据库 Schema Migration
Schema Migration 用版本化变更把数据库结构从旧状态安全演进到新状态,同时处理已有数据、约束、回滚边界与部署兼容性。
#concept
#growing
#backend
#database
[!info] related notes
- 所属 MOC: 数据库 MOC
- 前置概念: Schema、约束
- 并列概念: 数据库 Schema Migration 与发布兼容性
- 相关实践: 数据库迁移设计(golang-migrate)
数据库 Schema Migration
一句话定义
Schema Migration 是把数据库结构从版本 vN 明确地演进到 vN+1 的版本化变更记录;它描述“怎么改变”,而 ORM Model/Schema 更接近“现在应该长什么样”。
Model 与 Migration 的边界
Model / ORM Schema
→ 当前状态快照
Migration
→ 从旧状态走到新状态的历史
因此已经执行并发布的 Migration 原则上不应被直接改写;下一次结构变化应该新增一条 Migration,否则新环境重建出的历史可能和生产环境实际执行过的历史不同。
up / down
up 描述向前演进,例如:
ALTER TABLE users ADD COLUMN status TEXT;
down 描述如何撤销结构变化。但必须记住:
Schema rollback 不等于 Data rollback。
如果 up 删除了一列及其数据,down 重新创建同名列也无法自动恢复原数据。
已有数据是 Migration 的核心难点
生产表已有数据时,最终目标是 NOT NULL 并不意味着应该直接一步加上:
add nullable/default column
→ backfill existing rows
→ verify
→ add NOT NULL / CHECK / FK
Migration 设计不仅考虑最终 schema,还要设计旧数据如何合法地穿过中间状态。
Migration Ledger
迁移工具通常维护已执行记录,例如 schema_migrations / migrations 表,用来保证每个版本只执行一次并维持顺序。
与应用部署的兼容性
如果新版代码先开始读取 users.status,而数据库 Migration 尚未执行:
TypeScript / ORM 类型可能通过 ✅
应用甚至可能启动 ✅
真正执行相关 SQL 时
→ column does not exist ❌
因此生产发布需要设计应用版本与 Schema 版本的兼容窗口。复杂变更通常使用 expand-and-contract、回填、双读/双写等渐进策略。
边界与常见误解
- Migration 不是“把数据库从服务器 A 搬到服务器 B”。
- 自动
sync/alter适合部分开发场景,但不应替代可审查、可重复的生产迁移历史。 - 可回滚不代表所有变更都应该在线直接
down;数据破坏、长锁表和大规模回填都需要独立风险评估。