跳到主要内容

迁移工具

问题

主流的数据库迁移工具有哪些?它们的原理和使用方式是什么?

答案

一、迁移工具的核心原理

所有迁移工具都遵循相同的基本模型:

核心机制

  1. 迁移文件按有序版本号排列
  2. 数据库中有一张历史记录表schema_migrations / flyway_schema_history
  3. 执行时对比文件列表与历史表,只执行未执行的迁移
  4. 每次执行后将版本号写入历史表

二、独立迁移工具

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

维度FlywayLiquibase
变更格式纯 SQLXML / 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)灵活、多格式
PrismaORMTS类型安全、声明式
TypeORMORMTS与 Entity 同步
DjangoORMPython与 Model 紧密集成
AlembicORMPythonSQLAlchemy 配套
golang-migrate独立Go轻量、CLI

常见面试问题

Q1: 迁移文件的版本冲突如何处理?

答案

多人同时创建迁移文件时可能冲突(版本号重复/依赖错乱):

  1. 时间戳版本号:使用 20240101120000 格式而非序号,减少冲突
  2. CI 检查:在 CI 中运行 migrate status 检查是否有分叉
  3. 合并策略:发现冲突时,删除本地迁移文件,重新生成

Q2: 为什么已执行的迁移文件不能修改?

答案

因为迁移文件可能已在其他环境(测试、生产)执行过。修改后:

  • Checksum 校验失败:Flyway 等工具会检测文件变更并报错
  • 不一致:已执行环境的数据库状态与文件描述不符
  • 难以追溯:破坏了变更审计链

正确做法:创建新的迁移文件来修正问题。

Q3: 迁移失败了怎么办?

答案

迁移失败 → 检查失败原因
├── SQL 语法错误 → 修复后重新执行(或标记为已解决并创建新迁移)
├── 数据冲突 → 先处理数据再重新执行
└── 部分执行 → 手动回滚已执行的部分,修复后重跑

关键原则:每个迁移文件应该是幂等的或者包裹在事务中(DDL 事务支持依赖数据库,MySQL DDL 不支持事务,PostgreSQL 支持)。

相关链接