综合 goose 迁移:开发用时间戳、上线前 fix 转顺序号,Down 段没写就没有回滚

2026-11-04 21:31:48

goose 迁移笔记:SQL 文件写法、版本号策略与内嵌进二进制

pressly/goose 是 Go 写的数据库迁移工具,既是 CLI 也是库,MIT 协议,GitHub 上约 11.6k star。项目地址:,文档站:。它支持 Postgres、MySQL、MariaDB、Spanner、SQLite、YDB、ClickHouse、MSSQL、Vertica 等数据库,迁移可以写成 SQL 文件,也可以写成 Go 函数;此外还支持内嵌迁移、out-of-order、种子数据,以及 SQL 文件里的环境变量替换。

安装

go install github.com/pressly/goose/v3/cmd/goose@latest
brew install goose

需要精简二进制时,用构建标签排除不需要的驱动:

go build -tags='no_postgres no_mysql no_sqlite3 no_ydb' -o goose ./cmd/goose

可用的标签有:no_azuresql no_clickhouse no_libsql no_mssql no_mysql no_postgres no_sqlite3 no_vertica no_ydb。注意,no_mssql 也会同时排除 azuresql。

命令

调用形式是 goose DRIVER DBSTRING [OPTIONS] COMMAND,也可以先把 GOOSE_DRIVER / GOOSE_DBSTRING / GOOSE_MIGRATION_DIR 设好。驱动名包括 postgres mysql sqlite3 spanner mssql azuresql redshift tidb clickhouse ydb starrocks turso。

命令作用
up应用全部未应用的迁移,每个文件打印 OK
up-to VERSION升到指定版本
up-by-one只升一个版本
down回滚最近一个迁移
down-to VERSION回滚到指定版本,down-to 0 回滚全部(慎用)
redo回滚最近一个再重新应用
reset回滚全部
status打印 Applied At / Migration,未应用的显示 Pending
version打印当前版本
create NAME [sql|go]生成迁移文件
fix把时间戳版本改成顺序号
validate校验迁移
env打印环境变量配置

create 的两种编号:

goose create add_some_column sql      # 20170506082420_add_some_column.sql
goose -s create add_some_column sql   # 00001_add_some_column.sql

SQL 迁移文件的结构

每个文件必须恰好有一个 -- +goose Up 注解,-- +goose Down 可选;两者都有时,Up 必须写在前面。Up 之后的语句用于正向迁移,Down 之后的语句用于回滚。

默认整个文件运行在一个事务里。CREATE DATABASE 这类不能在事务中执行的语句,要在文件顶部加 -- +goose NO TRANSACTION,这个文件里的 Up 和 Down 都会跳过事务。

语句以分号切分,查询语句也要以分号结尾。PL/pgSQL 这类函数体内部带分号的语句,用 -- +goose StatementBegin 和 -- +goose StatementEnd 把相关片段包起来。

环境变量替换默认关闭,需要先用 -- +goose ENVSUB ON 打开,一直到 -- +goose ENVSUB OFF 关闭;同一个文件里可以多次开关。支持的写法有 ${VAR} 或 $VAR、${VAR:-default}、${VAR-default}、${VAR?err_msg};${VAR:?err_msg} 不支持。在 PL/pgSQL 里,建议只把需要替换的片段包起来,避免 $$ 被转义处理。

MySQL 的两个连接参数

用 mysql 驱动时必须带上 parseTime=true;一个 SQL 文件里写了多条分号分隔的语句时,还必须带 multiStatements=true,否则多条语句跑不了。

goose mysql "user:password@/dbname?parseTime=true&multiStatements=true" status

版本号:开发用时间戳,上线前 fix

create 默认用当前时间戳作为版本号。默认策略不允许 out-of-order:如果补跑一个版本号比当前版本小的缺失迁移,就会直接报错。需要补跑时,用 -allow-missing,或者在库方式里传 goose.WithAllowMissing()(作用于 Up、UpTo、UpByOne)。

推荐的混合做法是:开发阶段用时间戳版本,上线前在 CI pipeline 里跑 goose fix,把待发布的迁移转成顺序号,同时保留按时间戳排序的相对顺序。create -s 则直接生成 00001 这类顺序号。

内嵌到 Go 二进制

用 Go 1.16 的 embed 把 SQL 迁移打进程序:

//go:embed migrations/*.sql
var embedMigrations embed.FS

goose.SetBaseFS(embedMigrations)
goose.SetDialect("postgres")
goose.Up(db, "migrations")

目录参数要带上 embed 时的目录结构,这里传的是 "migrations"。内嵌只用于应用已有迁移,fix / create 仍然操作文件系统。

Go 函数式迁移需要自己编译 goose 二进制:import github.com/pressly/goose,在迁移文件的 init 里 goose.AddMigration(Up, Down) 注册,再用 import _ ".../migrations" 引入,最后调用 goose.Up(db, dir)。迁移文件名必须以数字加下划线开头,且不能以 _test.go 结尾。

func init() {
goose.AddMigration(Up, Down)
}

func Up(tx *sql.Tx) error {
_, err := tx.Exec("UPDATE users SET username='admin' WHERE username='root';")
return err
}

版本表名与非 public schema

-table 指定版本表名,默认是 goose_db_version。迁移不在 public schema 时,要写成 -table='schemaname.goose_db_version'。默认迁移运行在 public schema 上。

环境变量与 .env

支持 GOOSE_DRIVER、GOOSE_DBSTRING、GOOSE_MIGRATION_DIR、GOOSE_TABLE 四个变量:

GOOSE_DRIVER=postgres
GOOSE_DBSTRING=postgres://...
GOOSE_MIGRATION_DIR=./migrations
GOOSE_TABLE=custom.goose_migrations

.env 默认加载,-env=none 关闭,-env path 指定具体文件。

其他常用选项还有 -dir(默认 .)、-no-versioning(按文件顺序执行、不记版本)、-timeout、-allow-missing,以及 MySQL 专用的 -certfile / -ssl-cert / -ssl-key。

取舍

goose 只负责按版本执行迁移、记录已应用版本,不负责生成迁移内容:create 出来的是空壳文件,表结构得自己写。回滚同理,down / down-to 0 执行的是你在 Down 段里写的语句,如果 Down 段没有写清楚回滚逻辑,数据不会被还原。

版本号混用是实际项目里最容易出问题的地方:默认 out-of-order 报错,意味着顺序号和时间戳不能随缘切换,要么统一改成顺序号,要么在补跑时明确加 -allow-missing。ClickHouse 只完整支持部分部署模式,配置需要额外注意(未实测)。

复制全文 生成海报 goose 数据库迁移 Go SQL

推荐文章

程序员茄子在线接单