You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang Migrate版本命名与批量迁移历史记录问题求助

问题解答

1. 版本命名支持语义化版本(如1.1.1)

golang-migrate核心设计基于整数版本号,不直接支持带小数点的语义化版本,可通过以下两种方案实现类似效果:

方案1:整数编码语义化版本

将语义化版本转换为固定长度整数,比如主版本、次版本、补丁版本各占3位(不足补0):

  • 1.1.1 → 1001001
  • 2.0.5 → 2000005

迁移文件遵循官方命名规则:

1001001_initial_setup.up.sql
1001001_initial_setup.down.sql
2000005_add_user_table.up.sql
2000005_add_user_table.down.sql

同时可在自定义schema_migrations_history表中新增semantic_version字段,记录原始语义化版本:

ALTER TABLE schema_migrations_history ADD COLUMN semantic_version VARCHAR(20);

每次迁移时,可在UP语句中手动插入对应语义化版本:

INSERT INTO schema_migrations_history(version, semantic_version) VALUES (1001001, '1.1.1');

方案2:自定义Source驱动(不推荐)

若必须直接使用字符串版本号,需基于官方file source驱动二次开发修改版本解析逻辑,但该方式脱离官方维护,后续升级易出问题,不建议新手使用。


2. 批量迁移时记录所有历史版本

问题根源:golang-migrate的schema_migrations表仅存一行数据,批量迁移时仅UPDATE该行version字段,而你的触发器监听AFTER INSERT事件,因此不会触发;同时m.Up()一次性执行所有待迁移版本,无法逐个记录。

解决方案1:修改触发器为监听UPDATE事件

修改track_applied_migration函数及触发器,监听schema_migrations的UPDATE操作:

CREATE OR REPLACE FUNCTION track_applied_migration()
RETURNS TRIGGER AS $$
DECLARE _current_version BIGINT;
BEGIN
    SELECT COALESCE(MAX(version), 0) FROM schema_migrations_history INTO _current_version;
    IF NEW.dirty = 'f' AND NEW.version > _current_version THEN
        INSERT INTO schema_migrations_history(version) VALUES (NEW.version);
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';

DROP TRIGGER IF EXISTS track_applied_migrations ON schema_migrations;
CREATE TRIGGER track_applied_migrations
AFTER UPDATE ON schema_migrations FOR EACH ROW EXECUTE PROCEDURE track_applied_migration();

解决方案2:逐个执行迁移并手动记录(更可靠)

放弃m.Up(),改用循环调用m.Steps(1)逐个执行迁移,每次执行后手动记录版本到历史表。修改db.go中的Migrate函数:

func Migrate(sqlDB *sql.DB, migrationsDir string) error {
    driver, err := pgmigrate.WithInstance(sqlDB, &pgmigrate.Config{})
    if err != nil {
        return err
    }

    m, err := migrate.NewWithDatabaseInstance(
        "file://"+migrationsDir,
        "postgres", driver)
    if err != nil {
        return err
    }
    defer m.Close()

    if err := initMigrationHistory(sqlDB); err != nil {
        return err
    }

    for {
        err := m.Steps(1)
        if err != nil {
            if err == migrate.ErrNoChange {
                break
            }
            return err
        }

        currentVersion, _, err := m.Version()
        if err != nil {
            return err
        }

        var exists bool
        err = sqlDB.QueryRow("SELECT EXISTS(SELECT 1 FROM schema_migrations_history WHERE version = $1)", currentVersion).Scan(&exists)
        if err != nil {
            return err
        }
        if !exists {
            _, err = sqlDB.Exec("INSERT INTO schema_migrations_history(version) VALUES ($1)", currentVersion)
            if err != nil {
                return err
            }
        }
    }

    fmt.Println("Migration successful")
    return nil
}

func initMigrationHistory(sqlDB *sql.DB) error {
    statements := `
    BEGIN;
    SET ROLE 'postgres';
    CREATE TABLE IF NOT EXISTS schema_migrations_history (
        id SERIAL PRIMARY KEY,
        version BIGINT NOT NULL,
        applied_at timestamptz NOT NULL DEFAULT NOW()
    );
    COMMIT;`
    _, err := sqlDB.Exec(statements)
    return err
}

额外建议

  • 移除UpdateMigrationHistory函数中的触发器创建逻辑,改用上述方案之一
  • 为schema_migrations_history表的version字段添加唯一约束,避免重复插入:
ALTER TABLE schema_migrations_history ADD CONSTRAINT unique_version UNIQUE(version);

内容的提问来源于stack exchange,提问作者prag-sri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 12:44:50