news 2026/10/1 8:27:42

migrate 快速上手指南:用 CLI 与 Golang 库管理数据库迁移的全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
migrate 快速上手指南:用 CLI 与 Golang 库管理数据库迁移的全流程
  • 数据库
  • 开发工具
  • CLI

【免费下载链接】migrate

Database migrations. CLI and Golang library.

项目地址:https://gitcode.com/gh_mirrors/mi/migrate
点击查看免费下载

这篇指南以 GETTING_STARTED.md 为主体,系统讲解 golang-migrate(即 migrate)从零开始的完整使用流程:创建迁移文件、填充内容、执行迁移、测试回滚,以及处理失败迁移留下的脏(dirty)状态。读者学完后将掌握 migrate CLI 的核心命令与参数、up/down 迁移文件规范,并能结合源码理解底层版本记录与加锁机制,直接用于实际项目。

开始之前:理解 up/down 迁移概念

在动手之前,首先要理解数据库迁移中两个基础方向:

  • forward / up:将数据库从当前版本迁移到更新版本,通常执行CREATE TABLE、ALTER TABLE等变更;
  • reverse / down:将数据库回退到之前的版本,通常执行DROP TABLE等反向操作。

migrate 的核心模型是:一个逻辑迁移由一对文件表示——.up文件前进、.down文件回退,二者共享同一个版本号。例如仓库中的真实示例 1085649617_create_users_table.up.sql 与 1085649617_create_users_table.down.sql,up 文件创建users表,down 文件则负责删除它。这一"一对文件"设计的具体规范可参考 MIGRATIONS.md。

其次,为你的应用配置好一个数据库,并确认所使用的数据库驱动已被支持。migrate 的驱动列表维护在 README.md,覆盖 PostgreSQL、PGX(v4/v5)、Redshift、MySQL/MariaDB、SQLite/SQLite3/SQLCipher、Cassandra/ScyllaDB、ClickHouse、CockroachDB、YugabyteDB、MongoDB、Neo4j、SQL Server、Spanner、Firebird、rqlite 等众多数据库。数据库连接串统一使用 URL 形式:dbdriver://username:password@host:port/dbname?param1=true&param2=false,URL 中的保留字符(如!、#、$、%、@、?等)需要先做百分号编码(Percent-Encoding)。

创建迁移文件

使用 migrate CLI 的create子命令创建迁移。官方示例:

migrate create -ext sql -dir db/migrations -seq create_users_table

该命令会在db/migrations目录下生成一对以 6 位序列号开头的文件:

000001_create_users_table.up.sql 000001_create_users_table.down.sql

创建后,你只需要向这两个文件中填充 SQL 内容即可。

迁移文件名规范

create生成的文件名遵循 migrate 全局解析规则。在 source/parse.go 中定义了文件名正则:

Regex = regexp.MustCompile(`^([0-9]+)_(.*)\.(up|down)\.(.*)$`)

即文件名必须是{version}_{title}.{up|down}.{extension}形式:版本号(任意 64 位无符号整数)、标题(仅作可读性,不参与逻辑)、方向(up/down)、扩展名(如.sql)。所有迁移按版本号升序执行 up,降序执行 down。从源码结构看,Parse函数会提取Version、Identifier、Direction字段,供后续调度使用。

序列号模式与时间戳模式

create支持两种版本生成策略(见 internal/cli/main.go 与 internal/cli/commands.go 的实现):

  • 序列模式(-seq):默认 6 位数字(-digits N可自定义位数),自动取目录中已存在的最大序号并加 1(nextSeqVersion逻辑)。当序号位数不足时会报错提示。
  • 时间戳模式(默认):默认使用 Go 时间格式20060102150405(即YYYYMMDDHHMMSS,可通过-format指定其他 Go 时间格式,或使用特殊值unix/unixNano),时区默认 UTC(可用-tz指定)。若两个迁移落在同一时间戳,createCmd会检测到duplicate migration version错误并拒绝生成。

create的完整参数为:-ext E(扩展名,必填)、-dir D(目录,默认当前工作目录)、-seq、-digits N(默认 6)、-format(时间格式)、-tz(时区)。此外create使用O_EXCL独占模式创建文件,防止覆盖已存在的迁移。

填充迁移内容:幂等性与事务

创建文件之后,重点在于如何写出健壮的迁移内容。官方文档给出了三条关键建议:

1. 关注多开发者协作下的迁移一致性

IMPORTANT:在多人开发的项目中,存在迁移不一致的风险——例如两位开发者创建了冲突的迁移,而后创建迁移的那位开发者反而先合入仓库。开发团队应在代码评审时特别留意这类情况。这是真实世界的工程问题,migrate 官方 issue 中有过详细讨论与总结,建议团队制定明确的迁移命名与合入纪律(如约定合入前检查db/migrations目录的版本冲突)。

2. 尽量让迁移幂等

考虑让迁移具备幂等性——即同一段 SQL 连续执行两次应得到相同结果,这会让迁移更健壮。但幂等也有代价:它削弱了对数据库 schema 的控制。文档中的经典例子:

  • 假设你忘记在 down 迁移中DROP TABLE;
  • 执行 down 迁移后表仍然存在;
  • 再次执行 up 迁移时,普通CREATE TABLE会报错——这反而帮助你发现了 down 迁移中的问题;
  • 而如果用了CREATE TABLE IF NOT EXISTS,则不会报错,问题被悄悄掩盖。

因此,"是否幂等"要权衡使用:在确保 down 迁移正确清理的前提下,再考虑用IF NOT EXISTS等幂等写法增强健壮性。

3. 多条命令请用事务包裹

如果一次迁移中包含多条命令/查询,应将其包裹在事务中(前提是你的数据库支持事务 DDL)。这样一旦其中某条命令失败,整个数据库保持原状,避免半途而废的脏状态。从源码角度看,每个数据库驱动都实现了Run等接口来执行迁移,事务行为由驱动各自处理,因此"是否支持事务 DDL"(例如 PostgreSQL 支持、而某些数据库不支持)取决于所选数据库。

运行迁移

创建并填充好迁移后,通过 CLI 或你的应用来执行迁移,并检查预期变更是否生效。CLI 的基本用法:

migrate -database YOUR_DATABASE_URL -path PATH_TO_YOUR_MIGRATIONS up

-path是-source=file://path的简写(见 internal/cli/main.go 的翻译逻辑),两者等价。migrate 将迁移源(本地文件系统、io/fs、GitHub、GitLab、S3、GCS 等,见 README.md)与数据库驱动解耦:源码从 source 读取,按序应用到 database。

常用命令一览

migrate CLI 提供的完整命令集(源码见 internal/cli/main.go):

命令作用
create ...创建一对 up/down 迁移文件
goto V迁移到指定版本 V
up [N]应用全部(或 N 个)up 迁移
down [N] [-all]应用 N 个 down 迁移;-all应用全部(默认需要 y/N 确认)
drop [-f]清空数据库全部内容(默认需要确认,-f跳过)
force V直接设置版本 V 而不执行迁移(忽略 dirty 状态)
version打印当前迁移版本

常用全局选项:-source、-path、-database、-prefetch N(预读迁移数,默认 10)、-lock-timeout N(获取数据库锁的超时秒数,默认 15)、-verbose、-version、-help。

例如只执行前两个迁移:

migrate -source file://path/to/migrations -database postgres://localhost:5432/database up 2

若迁移托管在远程仓库,source 换成对应驱动即可,例如-source github://mattes:personal-access-token@mattes/migrate_test。CLI 收到 SIGINT(Ctrl+C)时会在安全断点优雅停止(通过Migrate.GracefulStop通道实现),若需立即终止可发送 SIGKILL。CLI 安装方式(预编译二进制、Homebrew、scoop、deb 包、go install)详见 cmd/migrate/README.md。

在应用代码中使用

migrate 同时是 Golang 库,只需把代码加进你的应用即可运行。最简单的用法(源码见 README.md):

import ( "github.com/golang-migrate/migrate/v4" _ "github.com/golang-migrate/migrate/v4/database/postgres" _ "github.com/golang-migrate/migrate/v4/source/file" ) func main() { m, err := migrate.New( "file:///migrations", "postgres://localhost:5432/database?sslmode=enable") if err != nil { // 处理错误 } m.Up() // 或 m.Steps(2) 显式指定执行条数 }

底层Migrate对象(migrate.go)提供了Up()、Down()、Steps(n)、Migrate(version)、Force(version)、Version()、Drop()等方法,并支持PrefetchMigrations预读与LockTimeout加锁超时配置,还自带优雅停止与线程安全设计。若你已有数据库连接(*sql.DB),可改用migrate.NewWithDatabaseInstance配合WithInstance构造驱动实例。

提交前的验证流程

在提交迁移之前,官方建议执行完整的"往返"验证:

  1. 运行 up 迁移;
  2. 运行 down 迁移;
  3. 再次运行 up 迁移。

这能确认迁移双向都正常工作。例如:如果 up 迁移创建了表而对应的 down 迁移没有删除它,那么再次执行 up 时就会遇到错误,问题在提交前就会被发现。

同时建议在独立的容器化环境中验证迁移(例如结合 Docker 测试工具 dktest、dockertest 等,在隔离容器里跑真实的数据库实例做冒烟测试),避免污染本地开发环境。

多实例部署必须使用支持锁定的数据库

IMPORTANT:如果要在不同机器上运行应用的多个实例,务必使用支持迁移锁定的数据库,否则多个实例并发执行迁移可能引发问题。这一建议与源码设计一致:Migrate在执行前会调用lock()获取数据库锁,DefaultLockTimeout为 15 秒(超时返回ErrLockTimeout),并在执行结束后释放。以 PostgreSQL 驱动为例(database/postgres/postgres.go),驱动会维护一张schema_migrations表(version bigint not null primary key, dirty boolean not null),通过SetVersion记录版本与脏标记,从而为并发安全提供基础。

处理失败的迁移:force 命令与 dirty 状态

当某条迁移执行出错时,migrate 会阻止你在同一数据库上继续执行其他迁移。你会看到形如Dirty database version 1. Fix and force version的错误——这正是 migrate.go 中ErrDirty的原始文案,意味着数据库已被标记为dirty(脏)。

此时你需要:

  1. 调查迁移错误:判断这次失败的迁移是部分应用了,还是完全没应用;
  2. 用force命令修正版本记录,使其反映数据库的真实状态:
migrate -path PATH_TO_YOUR_MIGRATIONS -database YOUR_DATABASE_URL force VERSION

例如,若错误迁移完全没执行,就force回它之前的版本;若它已部分执行(如表已建好),则force到该版本本身。修复迁移内容后,再force到正确版本,数据库即恢复 clean,可以继续迁移。

force的底层实现是Migrate.Force(version)(migrate.go):它直接调用驱动的SetVersion(version, false)写入版本并把 dirty 标记重置为 false,不检查当前版本、不执行任何迁移(因此也能忽略 dirty 状态使用)。CLI 层面force V要求参数V >= -1(-1 表示无迁移的初始版本)。官方 issue 中也有针对 force 用法与示例的详细讨论,可作参考。

进一步阅读

  • PostgreSQL 实战教程:以 PostgreSQL 为例的完整演练;
  • 迁移最佳实践:文件名格式、内容格式、可逆性等深入规范;
  • FAQ:常见问题解答(如为什么每个迁移要分 up/down 两个文件);
  • CockroachDB 实战教程;
  • 仓库源码:CLI 实现见 internal/cli/main.go 与 internal/cli/commands.go,核心库逻辑见 migrate.go 与 migration.go,文件名解析见 source/parse.go。
  • 数据库
  • 开发工具
  • CLI

【免费下载链接】migrate

Database migrations. CLI and Golang library.

项目地址:https://gitcode.com/gh_mirrors/mi/migrate
点击查看免费下载

相关推荐

上一篇:如何给AI助手写自定义技能?OpenClaw中文社区版openclaw-cn技能开发从零到一教程
下一篇:为什么VS Code连续10年霸榜IDE:Electron+Monaco双引擎架构完全解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 8:26:40

AI变声不自然?2026真实测评叮咚变声器,告别机械音

玩游戏开黑,线上聊天普遍的变声器,很多会存在操作复杂,有广告,音质不好等等问题,本次实测叮咚变声器,在普通环境下从音质,是否有隐形套路等等多方面去对比,测评仅个人 感受&#xff…

作者头像 李华
网站建设 2026/10/1 8:25:44

从零搞懂 Linux 字符设备驱动中的 ioctl:原理、实现与面试要点

本文以虚拟设备 vsim 为例,结合 cdev、file_operations、open 流程,把 ioctl 从用户空间到内核空间的完整链路讲清楚。适合驱动入门和面试复习。一、为什么需要 ioctlread/write 只能搬运数据流,无法表达"控制"语义。比如一个设备要…

作者头像 李华
网站建设 2026/10/1 8:24:52

冷链货物出了问题谁负责?承运方和货主的责任怎么划分

冷链货物出了问题谁负责?承运方和货主的责任怎么划分冷链货损一旦发生,货主说是运输环节冻坏的,承运方说装车时货就有问题,双方往往先吵责任、再回头找证据。责任划分不靠谁嗓门大,靠的是交接凭证、温控数据和合同条款…

作者头像 李华
网站建设 2026/10/1 8:24:48

企业福利商城排名

以下是2026年国内企业福利商城综合实力排名,榜单以‌合规资质、供应链能力、标杆客户覆盖、系统技术成熟度‌为核心排序依据,覆盖不同类型头部服务商:1. 众麦网络科技‌核心定位‌:国内少有的三位一体综合型头部服务商&#xff0c…

作者头像 李华