golang-migrate:18.8K Star 的数据库迁移工具,用 up/down SQL 文件管理变更
为什么需要 migrate
你可能经历过这种场景:
你改了数据库表结构——加了一个字段、改了一个索引。本地跑得好好的。
推了代码,同事拉下来一跑——报错。“users 表没有 phone 字段?”他那边数据库还是老的。
你发个消息:“你先执行一下这个 ALTER TABLE 语句。”同事执行了,又报错——因为他已经跑过另一个版本的迁移了,顺序不对。
多人协作、多个环境(开发、测试、预发、生产),数据库变更管理是最容易出问题的地方。代码有 Git,数据库变更谁来管?
migrate 就是干这个的。它让你用文件来管理数据库变更,每个变更有编号、有顺序、有回滚。像 Git 管代码一样管数据库。
migrate 是什么
| 项目 | 信息 |
|---|---|
| GitHub | github.com/golang-migrate/migrate |
| Star | 18.8K+ |
| 语言 | Go |
| 协议 | MIT |
| 版本 | v4 |
| 定位 | 数据库迁移工具 |
| 特色 | 支持 15+ 数据库、CLI + Go 库双模式 |
migrate 是 Go 生态里常用的数据库迁移工具,18.8K Star。
它支持 PostgreSQL、MySQL、SQLite、MongoDB、Cassandra、CockroachDB、ClickHouse、SQL Server、Google Cloud Spanner 等 15+ 种数据库。
两种使用方式:命令行工具(CLI)和 Go 库。大多数时候用 CLI 就够了。
核心概念
迁移文件
migrate 的核心是迁移文件。每个数据库变更对应一对文件:
migrations/
├── 000001_create_users_table.up.sql # 正向:创建表
├── 000001_create_users_table.down.sql # 反向:删除表
├── 000002_add_email_to_users.up.sql # 正向:加字段
├── 000002_add_email_to_users.down.sql # 反向:删字段
├── 000003_create_orders_table.up.sql # 正向:创建订单表
└── 000003_create_orders_table.down.sql # 反向:删除订单表
up.sql 是正向操作(创建、修改)。down.sql 是反向操作(回滚)。
文件名的数字前缀决定执行顺序。000001 先跑,000002 后跑。
迁移状态
migrate 在数据库里维护一张表(schema_migrations),记录当前执行到了哪个版本。
当前版本: 2
已执行: 000001, 000002
待执行: 000003
执行 migrate up 时,它会从当前版本开始,按顺序执行还没跑的 up.sql。
执行 migrate down 时,它会按倒序执行 down.sql,回滚变更。
“防弹”设计
migrate 自称 "bulletproof"(防弹)。什么意思?
- 事务安全:每个迁移文件在支持事务的数据库里(PostgreSQL、MySQL),整个文件在一个事务里执行。失败了自动回滚,不会留半成品。
- 幂等性:同一个迁移不会执行两次。migrate 记录版本号,跑过的跳过。
- 中断恢复:如果在迁移过程中进程被杀掉,下次启动时会检测到未完成的状态,提示你手动处理。不会默默继续。
快速开始
安装
# macOS
brew install golang-migrate
# Linux (下载二进制)
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.2.0/migrate.linux-amd64.tar.gz | tar xvz
sudo mv migrate /usr/local/bin/
# Go 安装
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
创建迁移文件
# 创建迁移目录
mkdir -p migrations
# 创建迁移文件
migrate create -ext sql -dir migrations -seq create_users_table
这会生成两个文件:
migrations/000001_create_users_table.up.sql
migrations/000001_create_users_table.down.sql
编辑 up.sql:
-- 000001_create_users_table.up.sql
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_users_email ON users(email);
编辑 down.sql:
-- 000001_create_users_table.down.sql
DROP INDEX IF EXISTS idx_users_email;
DROP TABLE IF EXISTS users;
执行迁移
# 执行所有 up 迁移
migrate -path migrations -database "postgres://user:pass@localhost:5432/mydb?sslmode=disable" up
# 输出:
# 2026/08/16 10:00:00 1/u create_users_table (15.23ms)
# 查看当前状态
migrate -path migrations -database "postgres://..." status
# 输出:
# At migration version 1
回滚
# 回滚一步
migrate -path migrations -database "postgres://..." down 1
# 回滚所有
migrate -path migrations -database "postgres://..." down
# 回滚到指定版本
migrate -path migrations -database "postgres://..." goto 0
实际使用场景
场景 1:团队协作
你和同事都在开发同一个项目。你加了一个迁移文件 000003_add_phone.up.sql,同事加了 000004_add_address.up.sql。
同事拉了你的代码后,只需要执行:
migrate -path migrations -database "postgres://..." up
migrate 会自动发现新的迁移文件,按顺序执行。不用手动告诉同事执行什么 SQL。
场景 2:多环境部署
开发、测试、生产三套环境。代码部署时,自动执行数据库迁移。
# 部署脚本 deploy.sh
#!/bin/bash
# 1. 先备份数据库
pg_dump $DATABASE_URL > backup_$(date +%Y%m%d).sql
# 2. 执行迁移
migrate -path migrations -database "$DATABASE_URL" up
# 3. 部署代码
git pull
go build -o app
systemctl restart app
每次部署,数据库自动跟代码同步。
场景 3:CI/CD 集成
在 CI 流水线中自动测试迁移。
# .github/workflows/ci.yml
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- name: Install migrate
run: |
curl -L https://github.com/golang-migrate/migrate/releases/download/v4.2.0/migrate.linux-amd64.tar.gz | tar xvz
sudo mv migrate /usr/local/bin/
- name: Run migrations
run: |
migrate -path migrations \
-database "postgres://postgres:test@localhost:5432/testdb?sslmode=disable" \
up
- name: Run tests
run: go test ./...
场景 4:Go 代码中集成
migrate 不只是 CLI 工具,也是 Go 库。可以在应用启动时自动执行迁移。
package main
import (
"log"
"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/source/file"
_ "github.com/golang-migrate/migrate/v4/database/postgres"
)
func main() {
m, err := migrate.New(
"file://migrations",
"postgres://user:pass@localhost:5432/mydb?sslmode=disable",
)
if err != nil {
log.Fatal(err)
}
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
log.Fatal(err)
}
log.Println("数据库迁移完成")
// 启动应用...
}
应用启动时自动检查并执行数据库迁移,不用额外操作。
迁移文件最佳实践
1. 每个文件只做一件事
-- ✅ 好的做法:一个文件一个操作
-- 000002_add_email_index.up.sql
CREATE INDEX idx_users_email ON users(email);
-- ❌ 不好的做法:一个文件一堆操作
-- 000002_everything.up.sql
CREATE TABLE users (...);
CREATE TABLE orders (...);
CREATE INDEX ...;
ALTER TABLE ...;
2. up 和 down 必须互逆
-- ✅ up.sql
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
CREATE INDEX idx_users_phone ON users(phone);
-- ✅ down.sql(必须完全相反)
DROP INDEX IF EXISTS idx_users_phone;
ALTER TABLE users DROP COLUMN IF EXISTS phone;
3. 使用 IF EXISTS / IF NOT EXISTS
-- ✅ 安全操作
CREATE TABLE IF NOT EXISTS users (...);
DROP INDEX IF EXISTS idx_old;
-- ❌ 不安全(表已存在会报错)
CREATE TABLE users (...);
4. 大表操作要分步
-- ❌ 一次性改大表(锁表,影响线上)
ALTER TABLE huge_table ADD COLUMN new_col VARCHAR(255);
-- ✅ 分步操作
-- 迁移 1:加列,允许 NULL
ALTER TABLE huge_table ADD COLUMN new_col VARCHAR(255);
-- 迁移 2:后台填充数据
UPDATE huge_table SET new_col = default_value WHERE new_col IS NULL;
-- 迁移 3:加约束
ALTER TABLE huge_table ALTER COLUMN new_col SET NOT NULL;
跟其他方案对比
| 维度 | migrate(golang-migrate) | Atlas | Ent |
|---|---|---|---|
| 语言 | Go | Go | Go |
| 方式 | SQL 文件 | Schema 声明 | Go 代码 |
| 数据库支持 | 15+ | 多 | 少 |
| Star | 18.8K | 7K+ | 15K+ |
| 学习成本 | 低 | 中 | 高 |
| 回滚支持 | ✅ | ✅ | ✅ |
| CLI | ✅ | ✅ | ❌ |
migrate 的优势在简单和通用。SQL 文件是最通用的方式,不依赖特定框架。写 SQL 的开发者都会用,学习成本几乎为零。
不足
说几个用起来不太顺的地方:
文件名格式死板。必须用 000001_xxx.up.sql 格式。多人协作时,两个人都创建了 000003,合并时会冲突。虽然 migrate 提供 -seq 自动编号,但冲突了还是得手动改。
没有 dry-run 模式。不能预览会执行哪些 SQL。只能执行了才知道。对于生产环境,这个功能很需要。
大表迁移需要自己处理。migrate 不会帮你自动分批处理大表。ALTER TABLE 锁表怎么办?得分步操作。这些都得自己写 SQL。
错误信息不够清晰。迁移失败了,有时候报错信息不够明确,得自己看日志找原因。
不支持数据迁移。migrate 主要处理 schema 变更(DDL)。数据迁移(DML)虽然也能写在 SQL 文件里,但没有专门的支持。
适用场景
migrate 适合什么情况?
多人协作项目。多人同时改数据库表结构,需要统一管理变更。migrate 是标准做法。
多环境部署。开发、测试、生产三套环境,数据库要保持同步。migrate 可以集成到部署脚本里。
CI/CD 流水线。自动化测试和部署,数据库迁移也要自动化。
Go 项目。Go 生态里最成熟的数据库迁移工具,社区大,文档全。
任何需要数据库版本管理的项目。不管什么语言,migrate 的 CLI 都能用。
结语
数据库变更管理,多人协作的噩梦。你加个字段他删个字段,环境不同步,代码跑不起来。
migrate 换了个思路:用文件管理数据库变更。每个变更一对 SQL 文件(up + down),按编号顺序执行,支持回滚。简单、通用、可靠。
18.8K Star,Go 写的,支持 15+ 种数据库。CLI 工具和 Go 库双模式,满足各种需求。
如果你的项目还没有数据库迁移工具,还在手动执行 SQL 脚本、靠人传人通知数据库变更——花一个小时接入 migrate。
参考链接
- migrate GitHub: github.com/golang-migrate/migrate
- migrate 文档: github.com/golang-migrate/migrate/tree/master/cmd/migrate
- Atlas(Schema 声明式迁移): atlasgo.io
- Ent(Go ORM 框架): entgo.io