Skip to content
Charles
Go back

Rust 数据库迁移方案对比

Edit page

Rust 没有一个所有项目都会使用的数据库迁移工具。原因很简单:Rust 的数据库访问层本身就有明显分流,使用 SQLx、Diesel 和 SeaORM 时,最自然的迁移方案通常也是各自生态提供的方案。

数据库迁移真正要解决的不是“如何执行一段 SQL”,而是:

先给结论

场景推荐原因
使用 SQLx,偏 SQL-first、异步服务SQLx Migrations与 SQLx 连接池、异步运行时和 sqlx::migrate! 集成最直接
使用 DieselDiesel migrationsup.sql / down.sql、CLI、嵌入和 schema.rs 体系完整
使用 SeaORMSeaORM Migration使用 SeaQuery 或原始 SQL 写迁移,支持异步、种子数据和独立 migration crate
不想绑定 ORM,想嵌入 SQL / Rust migrationRefinery支持 SQL 与 Rust 模块、CLI 和嵌入,迁移历史独立于数据访问层
多语言团队、希望做 Schema-as-Code 和风险检查AtlasRust 不是它的核心绑定,但可以作为数据库平台工具统一生成、检查和执行迁移
本地原型或可随时重建的测试库简单 Schema 初始化不需要为一次性数据库设计完整的生产迁移流程

默认选择可以压缩成一句话:

版本化迁移是生产默认值

Rust 生态主流方案基本都是版本化迁移:每次数据库变化对应一个新的迁移文件或模块,工具在数据库中记录已经执行过的版本。

migrations/
├── 20260824190000_create_users.sql
├── 20260825100000_add_user_email.sql
└── 20260826120000_create_user_index.sql

已经在生产环境执行过的迁移应该视为不可修改的历史记录。发现问题时新增修复迁移,而不是编辑旧文件。否则本地代码和生产数据库可能都显示“版本相同”,实际执行内容却已经不同。

down、revert 或 rollback 只能表示工具具备执行反向 DDL 的能力,并不代表数据可以恢复。删除列之后再把列加回来,原来的数据不会自动出现;生产故障通常应采用向前修复,而不是直接回滚到旧 Schema。

方案一:SQLx Migrations

项目地址:github.com/launchbadge/sqlx,CLI 文档:sqlx-cli

SQLx 的迁移是典型的 SQL-first 方案。CLI 默认创建类似下面的文件:

migrations/
├── 20260824190000_create_users.sql
└── 20260825100000_add_user_email.sql

常用命令:

sqlx migrate add create_users
sqlx migrate run
sqlx migrate info

如果需要可逆迁移,可以在创建时加 -r:

sqlx migrate add -r create_users

这样会得到 .up.sql 和 .down.sql 两个文件。SQLx 也可以把迁移嵌入二进制:

static MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!();

MIGRATOR.run(&pool).await?;

默认目录是项目根目录下的 migrations,也可以显式指定路径:

static MIGRATOR: sqlx::migrate::Migrator =
    sqlx::migrate!("db/migrations");

优点

注意点

适合谁

使用 Axum、Actix Web 或其他异步 Rust Web 框架,并且愿意直接写 SQL 的团队。对于 SQLx 项目,额外引入 Diesel 或 Refinery 通常没有必要。

方案二:Diesel migrations

项目地址:diesel.rs,迁移 API:diesel_migrations

Diesel 使用目录加 up.sql / down.sql 的模式:

migrations/
└── 20260824190000_create_users/
    ├── up.sql
    └── down.sql

常用命令:

diesel setup
diesel migration generate create_users
diesel migration run
diesel migration redo

Diesel 的迁移文件本质上仍然是原始 SQL。执行每个迁移时,默认会使用独立事务;不能放进事务的数据库操作,可以通过 metadata.toml 关闭该迁移的事务包装。

Diesel 还可以根据 schema.rs 和数据库生成一个迁移起点:

diesel migration generate --diff-schema create_users

但生成结果不是最终答案。官方文档也明确提醒,生成的迁移需要人工调整,例如补充默认值或修正业务上的改名语义。

优点

注意点

适合谁

已经使用 Diesel 查询、schema.rs 和 Diesel CLI 的项目。Diesel 项目没有必要为了迁移单独引入通用工具,除非团队明确需要统一跨语言的数据库平台。

方案三:SeaORM Migration

项目地址:SeaORM,迁移文档:SeaORM Migration

SeaORM 把迁移放在独立的 migration crate 中,迁移可以使用 SeaQuery 的 Rust API,也可以执行原始 SQL。一个迁移包含 up 和 down 两个异步方法:

#[derive(DeriveMigrationName)]
pub struct Migration;

#[async_trait::async_trait]
impl MigrationTrait for Migration {
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .create_table(
                Table::create()
                    .table(User::Table)
                    .if_not_exists()
                    .col(ColumnDef::new(User::Id).integer().not_null().auto_increment().primary_key())
                    .col(ColumnDef::new(User::Name).string().not_null())
                    .to_owned(),
            )
            .await
    }

    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .drop_table(Table::drop().table(User::Table).to_owned())
            .await
    }
}

SeaORM CLI 的常用命令:

sea-orm-cli migrate init
sea-orm-cli migrate generate create_users
sea-orm-cli migrate up
sea-orm-cli migrate status
sea-orm-cli migrate down

SeaORM 会在数据库中创建迁移记录表,默认名称是 seaql_migrations。官方文档推荐把应用、实体和迁移拆成 Cargo workspace 中的不同 crate;这样迁移可以作为独立 Job 或 CLI 发布,也可以由应用调用 Migrator::up。

优点

注意点

适合谁

使用 SeaORM、偏异步、希望迁移也用 Rust API 表达的团队。SeaORM 的迁移 crate 已经覆盖了大多数日常需求,不需要再叠加 SQLx Migrations。

方案四:Refinery

项目地址:github.com/rust-db/refinery

Refinery 更接近独立的 Flyway 风格迁移器,不要求项目使用某个 ORM。它支持 SQL 文件,也支持返回 SQL 字符串的 Rust migration module:

migrations/
├── V1__create_users.sql
├── V2__add_user_email.sql
└── V3__create_user_index.rs

可以通过 embed_migrations! 把迁移编译进二进制:

mod embedded {
    use refinery::embed_migrations;

    embed_migrations!("./migrations");
}

embedded::migrations::runner().run(&mut connection)?;

也可以安装 CLI 执行迁移:

cargo install refinery_cli
refinery migrate -e DATABASE_URL -p ./migrations

Refinery 默认按迁移执行历史检查缺失和 divergent migration,并默认每个迁移使用独立事务;也可以把全部迁移放到一个事务中,但 MySQL 的 DDL 事务语义会限制这种做法。

优点

注意点

适合谁

使用 tokio-postgres、rusqlite、mysql_async、Tiberius 或自定义数据访问层,又希望迁移工具独立于 ORM 的团队。尤其适合想采用“只向前增加迁移,不在生产执行 down”的团队。

Atlas:不是 Rust 专属,但适合数据库平台化

项目地址:Atlas

Atlas 是语言无关的 Schema-as-Code 和迁移工具。它可以从 SQL、HCL 或 ORM Schema 计算差异,生成版本化迁移,并在 CI 中检查删除表、删除列、非空约束、表锁和数据依赖等问题。

它不是 Rust 项目的默认迁移库,因为 SQLx、Diesel、SeaORM 和 Refinery 都能独立完成版本化迁移。但以下场景值得考虑:

Atlas 的自动 diff 也不能识别所有业务语义。把 name 改成 display_name 时,工具可能生成删除旧列、增加新列;正确方案可能是增加新列、双写、回填,最后再删除旧列。生成的 SQL 仍然需要人工审查。

关键能力对比

产品SQL-firstRust migration迁移嵌入回滚模型ORM 绑定CI / Schema 检查适合生产默认值
SQLx Migrations弱,主要执行 SQLsqlx::migrate!可选 .down.sqlSQLx需自行组合
Diesel migrations支持 Rust migration sourceembed_migrations!down.sqlDieselschema.rs + CLI
SeaORM Migration支持强,Rust + SeaQuerymigration crate / 应用集成up / downSeaORMCLI + 类型模型
Refinery支持 Rust 模块embed_migrations!新增 forward migrationdivergence / checksum
Atlas支持通过 Schema / 外部集成不是核心用法版本化 / forward fix内置 diff / lint

这里的“适合生产默认值”指工具能否作为生产迁移流程的基础,不代表可以把迁移直接塞进应用启动,更不代表生成的 DDL 不会锁表。

推荐的生产工作流

1. 迁移作为独立发布步骤

不要让每一个 Web 实例都在启动时抢着执行迁移。比较稳妥的流程是:

构建应用和迁移工具

Pull Request:审查迁移 SQL / Rust migration

CI:空库执行全部迁移,检查状态和失败路径

部署 Job:只运行一次迁移

发布兼容新旧 Schema 的应用

如果确实需要应用启动迁移,应明确使用数据库锁或平台层的单实例 Job,并验证目标数据库的锁和事务语义。把迁移嵌入二进制只解决“文件是否随程序发布”,不解决“谁来执行”和“多个实例是否并发执行”。

2. 使用 expand / migrate / contract

例如把 users.name 改成 users.display_name,不要在一个迁移里直接删列:

  1. 增加 display_name,先允许为空或提供安全默认值。
  2. 新旧版本应用同时写入必要的字段。
  3. 分批回填历史数据,观察错误和延迟。
  4. 代码只读取新列,并确认旧版本已经下线。
  5. 最后再用独立迁移删除旧列。

字段重命名、非空约束、唯一索引和大表索引构建,都是比“迁移工具能不能执行”更重要的发布问题。

3. 正确处理事务边界

很多迁移默认在事务中执行,但数据库并不保证所有 DDL 都能回滚。比如某些数据库版本的索引并发构建、数据库级对象和表重写操作不能放在普通事务里。

每个迁移都应该确认:

4. 验证迁移历史

最小 CI 检查可以按工具组合:

# SQLx
sqlx migrate run
cargo sqlx prepare --check

# Diesel
diesel migration run
diesel migration redo

# SeaORM
sea-orm-cli migrate up
sea-orm-cli migrate status

# Refinery
refinery migrate -e DATABASE_URL -p ./migrations

测试数据库应尽量使用和生产相同的大版本。SQLite 上成功,不代表 PostgreSQL 或 MySQL 上的锁、事务、类型和 ALTER TABLE 行为相同。

最终选择

Rust 数据库迁移的关键,不是选择最“Rust 化”的 API,而是让迁移历史可审查、可重复执行、可观测,并与应用发布兼容。能用 SQL 清晰表达的结构变化,就不要为了追求类型安全把它包装成一层很长的 Rust 代码;真正需要业务逻辑的回填,才值得使用 Rust migration。

参考资料


Edit page
Share this post:

Previous Post
Go 数据库迁移方案对比