Golang Migrate版本命名与批量迁移历史记录问题求助
问题解答
1. 版本命名支持语义化版本(如1.1.1)
golang-migrate核心设计基于整数版本号,不直接支持带小数点的语义化版本,可通过以下两种方案实现类似效果:
方案1:整数编码语义化版本
将语义化版本转换为固定长度整数,比如主版本、次版本、补丁版本各占3位(不足补0):
1.1.1→10010012.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
相关产品推荐
相关产品推荐

