编程 golang-migrate:18.8K Star 的数据库迁移工具,用 up/down SQL 文件管理变更

2026-09-12 09:55:34

golang-migrate:18.8K Star 的数据库迁移工具,用 up/down SQL 文件管理变更

为什么需要 migrate

你可能经历过这种场景:

你改了数据库表结构——加了一个字段、改了一个索引。本地跑得好好的。

推了代码,同事拉下来一跑——报错。“users 表没有 phone 字段?”他那边数据库还是老的。

你发个消息:“你先执行一下这个 ALTER TABLE 语句。”同事执行了,又报错——因为他已经跑过另一个版本的迁移了,顺序不对。

多人协作、多个环境(开发、测试、预发、生产),数据库变更管理是最容易出问题的地方。代码有 Git,数据库变更谁来管?

migrate 就是干这个的。它让你用文件来管理数据库变更,每个变更有编号、有顺序、有回滚。像 Git 管代码一样管数据库。

migrate 是什么

项目信息
GitHubgithub.com/golang-migrate/migrate
Star18.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"(防弹)。什么意思?

  1. 事务安全:每个迁移文件在支持事务的数据库里(PostgreSQL、MySQL),整个文件在一个事务里执行。失败了自动回滚,不会留半成品。
  2. 幂等性:同一个迁移不会执行两次。migrate 记录版本号,跑过的跳过。
  3. 中断恢复:如果在迁移过程中进程被杀掉,下次启动时会检测到未完成的状态,提示你手动处理。不会默默继续。

快速开始

安装

# 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)AtlasEnt
语言GoGoGo
方式SQL 文件Schema 声明Go 代码
数据库支持15+
Star18.8K7K+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。

参考链接

复制全文 生成海报 Go 数据库 迁移工具 golang-migrate MySQL

推荐文章

程序员茄子在线接单