迁移工具
问题
主流的数据库迁移工具有哪些?它们的原理和使用方式是什么?
答案
一、迁移工具的核心原理
所有迁移工具都遵循相同的基本模型:
核心机制:
- 迁移文件按有序版本号排列
- 数据库中有一张历史记录表(
schema_migrations/flyway_schema_history) - 执行时对比文件列表与历史表,只执行未执行的迁移
- 每次执行后将版本号写入历史表
二、独立迁移工具
Flyway
Flyway 是 Java 生态最流行的迁移工具,以 SQL 脚本驱动为核心。
sql/
├── V1__Create_users_table.sql # 版本化迁移(V + 版本号 + 双下划线 + 描述)
├── V2__Add_email_column.sql
├── V3__Create_posts_table.sql
└── R__Refresh_views.sql # 可重复迁移(每次内容变化就重新执行)
V1__Create_users_table.sql
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
# 执行迁移
flyway migrate
# 查看状态
flyway info
# 校验(检查已执行的迁移文件是否被篡改)
flyway validate
# 回滚(Flyway Teams 付费功能)
flyway undo
Flyway 注意事项
- 命名规则严格:
V{版本号}__{描述}.sql,双下划线分隔 - 已执行的迁移文件不可修改(通过 checksum 校验)
- 社区版不支持 undo(回滚),需要手动编写反向 SQL
Liquibase
Liquibase 支持 XML/YAML/JSON/SQL 多种格式,更灵活。
changelog.yaml
databaseChangeLog:
- changeSet:
id: 1
author: alice
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: BIGINT
autoIncrement: true
constraints:
primaryKey: true
- column:
name: name
type: VARCHAR(100)
constraints:
nullable: false
- column:
name: email
type: VARCHAR(255)
constraints:
nullable: false
unique: true
rollback:
- dropTable:
tableName: users
liquibase update # 执行变更
liquibase rollback 1 # 回滚最近 1 个变更
liquibase status # 查看待执行
liquibase diff # 对比两个数据库差异
Flyway vs Liquibase
| 维度 | Flyway | Liquibase |
|---|---|---|
| 变更格式 | 纯 SQL | XML / YAML / JSON / SQL |
| 回滚支持 | 付费版(undo) | 免费(rollback 声明) |
| 数据库 diff | 不支持 | 支持(对比生成变更) |
| 学习成本 | 低(直接写 SQL) | 中(需学 DSL 格式) |
| 适合场景 | 简单直接、SQL 驱动 | 多数据库兼容、需要回滚 |
三、ORM 内置迁移工具
Prisma Migrate
# 创建迁移
npx prisma migrate dev --name add_age_column
# 生成的文件
# prisma/migrations/20240101120000_add_age_column/migration.sql
# 部署到生产
npx prisma migrate deploy
# 重置数据库
npx prisma migrate reset
TypeORM Migration
# 根据 Entity 变更自动生成迁移
npx typeorm migration:generate -d src/data-source.ts src/migrations/AddAgeColumn
# 手写迁移
npx typeorm migration:create src/migrations/SeedData
# 执行
npx typeorm migration:run -d src/data-source.ts
# 回滚最近一次
npx typeorm migration:revert -d src/data-source.ts
migrations/AddAgeColumn.ts
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
export class AddAgeColumn1704067200000 implements MigrationInterface {
async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn('users', new TableColumn({
name: 'age',
type: 'int',
default: 0,
isNullable: false,
}));
}
async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn('users', 'age');
}
}
Django Migrations
# 根据 Model 变更自动生成迁移
python manage.py makemigrations
# 执行迁移
python manage.py migrate
# 查看迁移状态
python manage.py showmigrations
# 回滚到指定迁移
python manage.py migrate app_name 0002
Alembic(SQLAlchemy)
# 自动生成迁移
alembic revision --autogenerate -m "add age column"
# 升级到最新
alembic upgrade head
# 回滚一步
alembic downgrade -1
四、迁移工具对比总结
| 工具 | 类型 | 语言 | 自动生成 | 回滚 | 特点 |
|---|---|---|---|---|---|
| Flyway | 独立 | Java | 否 | 付费 | 简单、SQL 驱动 |
| Liquibase | 独立 | Java | 是(diff) | 是 | 灵活、多格式 |
| Prisma | ORM | TS | 是 | 否 | 类型安全、声明式 |
| TypeORM | ORM | TS | 是 | 是 | 与 Entity 同步 |
| Django | ORM | Python | 是 | 是 | 与 Model 紧密集成 |
| Alembic | ORM | Python | 是 | 是 | SQLAlchemy 配套 |
| golang-migrate | 独立 | Go | 否 | 是 | 轻量、CLI |
常见面试问题
Q1: 迁移文件的版本冲突如何处理?
答案:
多人同时创建迁移文件时可能冲突(版本号重复/依赖错乱):
- 时间戳版本号:使用
20240101120000格式而非序号,减少冲突 - CI 检查:在 CI 中运行
migrate status检查是否有分叉 - 合并策略:发现冲突时,删除本地迁移文件,重新生成
Q2: 为什么已执行的迁移文件不能修改?
答案:
因为迁移文件可能已在其他环境(测试、生产)执行过。修改后:
- Checksum 校验失败:Flyway 等工具会检测文件变更并报错
- 不一致:已执行环境的数据库状态与文件描述不符
- 难以追溯:破坏了变更审计链
正确做法:创建新的迁移文件来修正问题。
Q3: 迁移失败了怎么办?
答案:
迁移失败 → 检查失败原因
├── SQL 语法错误 → 修复后重新执行(或标记为已解决并创建新迁移)
├── 数据冲突 → 先处理数据再重新执行
└── 部分执行 → 手动回滚已执行的部分,修复后重跑
关键原则:每个迁移文件应该是幂等的或者包裹在事务中(DDL 事务支持依赖数据库,MySQL DDL 不支持事务,PostgreSQL 支持)。