数据库迁移与版本管理
问题
如何管理数据库 Schema 变更?如何实现零停机迁移?
面试速答版
如何管理数据库 Schema 变更? 核心是「Schema as Code」,所有变更走迁移文件、进 Git、走 CI:
- 声明式(Prisma、Drizzle):改 schema 文件 → 工具自动 diff 生成 SQL 迁移。
- 代码式(TypeORM、Knex):手写
up()/down(),控制力更强。 - 流程:开发环境
migrate dev生成迁移文件 → PR Review → 生产migrate deploy只应用、不生成。 - 每个迁移文件需要有唯一名称 + 时间戳,表中用一张
_migrations表记录已应用的版本。
如何实现零停机迁移?
危险动作:ALTER 大表、删列、加 NOT NULL 不带默认值、改列类型都可能锁表。实践如下:
- 拆多步:加字段用三步「加可空列 → 双写回填数据 → 加上
NOT NULL」;删字段走「代码停读→下一版才 DDL」。 - 在线 DDL 工具:pt-online-schema-change、gh-ost,通过临时表 + binlog 同步避免锁表。
- 双向兼容:代码要能同时兼容「迁移前 / 迁移后」两种 Schema,才能灯度发布。
- 应急:迁移文件要可回滚,但生产一般不跳回滚,而是「向前修复」。
答案
迁移工具对比
| 工具 | 配合 ORM | 特点 |
|---|---|---|
| Prisma Migrate | Prisma | 声明式,自动生成 SQL |
| TypeORM Migration | TypeORM | 代码式,手写迁移 |
| drizzle-kit | Drizzle | 自动检测 Schema 差异 |
| knex migrations | Knex | 手写 up/down |
| golang-migrate | 无 | 纯 SQL 文件 |
Prisma 迁移流程
# 1. 修改 schema.prisma
# 2. 生成迁移文件
npx prisma migrate dev --name add_user_role
# 3. 生产环境应用迁移
npx prisma migrate deploy
schema.prisma
model User {
id String @id @default(uuid())
name String
email String @unique
role Role @default(USER) // 新增字段
}
enum Role {
USER
ADMIN
}
手写迁移(TypeORM)
migration.ts
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddUserRole1700000000000 implements MigrationInterface {
async up(queryRunner: QueryRunner): Promise<void> {
// 1. 添加列(允许 NULL,不影响现有数据)
await queryRunner.query(
`ALTER TABLE users ADD COLUMN role VARCHAR(20) DEFAULT 'USER'`,
);
// 2. 回填数据
await queryRunner.query(
`UPDATE users SET role = 'ADMIN' WHERE is_admin = true`,
);
// 3. 设置 NOT NULL(数据填充完毕后)
await queryRunner.query(
`ALTER TABLE users ALTER COLUMN role SET NOT NULL`,
);
}
async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE users DROP COLUMN role`);
}
}
零停机迁移策略
危险操作
直接修改列类型、删除列、添加 NOT NULL 列(无默认值)可能导致停机。
三步走策略(重命名列为例):
- 部署 1:添加新列,双写(新旧列都写)
- 部署 2:读从新列,迁移历史数据
- 部署 3:删除旧列
常见面试问题
Q1: 迁移文件要不要提交到 Git?
答案:
必须提交。 迁移文件是数据库版本的历史记录,和代码一样需要版本管理。团队成员拉取代码后执行迁移即可同步数据库结构。
Q2: 如何回滚迁移?
答案:
- 每个迁移文件都写
down方法 - 生产环境尽量不回滚,而是创建新的迁移修复
- 回滚前确认数据兼容性
Q3: 大表加索引会锁表吗?
答案:
MySQL 默认会锁表。PostgreSQL 可以用 CREATE INDEX CONCURRENTLY 不锁表。MySQL 可以用 pt-online-schema-change 工具。