数据库迁移

数据库迁移

go-web 使用 Goose 进行版本化 SQL 迁移。框架在两处使用 Goose:

  1. 运行时自动迁移 —— 服务启动时,migration.Migration Server 会自动执行 goose.Up,把所有未应用的迁移跑完
  2. CLI 工具 —— cmd/migrate/main.go 提供独立的命令行,可以手动 up / down / status / create / redo

配置

database:
  driver: "mysql"        # 支持 mysql / postgresql / sqlite,memory 不支持迁移
  migration:
    dir: "migrations"    # 迁移文件目录,相对工作目录

环境变量覆盖:DATABASE_MIGRATION_DIR=path/to/migrations

CLI 用法

# 创建新迁移文件
go run cmd/migrate/main.go create create_users_table

# 应用所有未执行的迁移
go run cmd/migrate/main.go up

# 回滚最近一次迁移
go run cmd/migrate/main.go down

# 查看迁移状态
go run cmd/migrate/main.go status

# 回滚最近一次然后重新执行
go run cmd/migrate/main.go redo

CLI 内部会执行最小装配链(Env → Config → Logger → Database),拿到 *gorm.DB 后转成 *sql.DB 交给 Goose。

迁移文件格式

执行 create 后会在 migrations/ 中生成形如 20260326120000_create_users_table.sql 的文件:

-- +goose Up
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(100) NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- +goose Down
DROP TABLE IF EXISTS users;

规则

  • -- +goose Up 是向上迁移,-- +goose Down 是回滚
  • 每个文件必须同时包含 Up 与 Down
  • Down 应能完全撤销 Up 的变更
  • 文件名时间戳决定执行顺序,不要手动修改时间戳
  • 不要修改已经在生产环境执行过的迁移文件;有问题就新建一个修复型迁移

多语句事务

默认每条 SQL 独立执行。如果需要在同一个事务里跑多条:

-- +goose Up
-- +goose StatementBegin
CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    amount DECIMAL(10,2) NOT NULL
);

INSERT INTO orders (user_id, amount) VALUES (1, 100.00);
-- +goose StatementEnd

-- +goose Down
DROP TABLE IF EXISTS orders;

启动时自动迁移

如果在 AppProvider.Servers() 中启用了 migration.Migration{},服务每次启动都会执行一次 goose.Up(等价于 CLI 的 up 命令)。这在 Kubernetes 等场景下非常方便:

  • 部署 = 重启 = 自动迁移
  • 多实例同时启动时,Goose 利用数据库锁保证迁移只跑一次

如果你不希望启动时自动迁移(例如想在 CI 里独立执行),只需在 config/app.goServers() 中注释掉 &migration.Migration{},然后通过 CLI 在部署前手动执行。

各驱动差异

驱动 迁移支持 Goose Dialect
mysql mysql
postgresql postgres
sqlite sqlite3
memory ——

memory 驱动用于 Demo 与单元测试,不支持持久化迁移。CLI 在检测到 database.driver=memory 时会直接报错。

实际示例

完整的"新增字段"迁移示例:

-- +goose Up
ALTER TABLE users
    ADD COLUMN nickname VARCHAR(64) NOT NULL DEFAULT '' AFTER username,
    ADD INDEX idx_users_nickname (nickname);

-- +goose Down
ALTER TABLE users
    DROP INDEX idx_users_nickname,
    DROP COLUMN nickname;

"重命名表"示例(注意必须可回滚):

-- +goose Up
RENAME TABLE old_users TO users;

-- +goose Down
RENAME TABLE users TO old_users;

常见问题

  • memory 驱动不支持版本化迁移 —— 切换到 mysql / postgresql / sqlite 之一
  • 不支持的数据库驱动: xxx —— database.driver 必须是 mysql / postgresql / sqlite 之一
  • 服务启动后立即退出 —— 检查迁移文件中是否有 SQL 语法错误,启动日志会打印第一条失败的迁移文件名
  • 多实例并发启动 —— Goose 通过 goose_db_version 表 + 数据库锁保护,无需额外协调