当前位置: 首页 > news >正文

数据库迁移:goose与migrate工具

数据库迁移:goose与migrate工具

摘要: 本篇讲解Go项目中数据库迁移工具的使用,包括goose的SQL迁移文件管理与Go代码嵌入迁移、golang-migrate的嵌入式迁移方案、版本回滚与灰度发布策略,分享迁移脚本中使用了新表结构导致回滚失败的踩坑经历,对比goose、golang-migrate与atlas三个工具的优劣。

开篇故事

去年我们团队遇到一次严重的生产事故。同事手动在数据库里加了一个字段,代码还没部署上去。结果线上代码找不到这个字段,接口全部报错。回滚代码也没用,因为数据库结构已经变了。

从那以后我们立了规矩,数据库结构变更必须用迁移工具管理,不能手动改。迁移工具的核心价值是把数据库变更纳入版本控制,每次变更都有记录,能前进也能回滚。这篇讲两个最常用的Go迁移工具:goose和golang-migrate。

一、goose工具:SQL迁移文件管理

goose是Go生态里最流行的迁移工具,支持SQL和Go两种迁移方式。

SQL迁移文件

goose的SQL迁移文件用注释标记up和down。

-- +goose Up-- 创建用户表CREATETABLEusers(id BIGSERIALPRIMARYKEY,usernameVARCHAR(50)NOTNULLUNIQUE,emailVARCHAR(100)NOTNULLUNIQUE,password_hashVARCHAR(255)NOTNULL,created_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP,updated_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP);CREATEINDEXidx_users_emailONusers(email);-- +goose Down-- 回滚:删除表DROPTABLEIFEXISTSusers;

在Go代码中嵌入迁移

goose v3支持把迁移文件嵌入到二进制里,部署时不需要带SQL文件。

packagemainimport("context""database/sql""embed""fmt""log""github.com/pressly/goose/v3"_"github.com/lib/pq")// 把migrations目录下的SQL文件嵌入到二进制中////go:embed migrations/*.sqlvarembedMigrations embed.FSfuncmain(){// 连接数据库db,err:=sql.Open("postgres","host=localhost port=5432 user=postgres dbname=myapp sslmode=disable")iferr!=nil{log.Fatal("数据库连接失败:",err)}deferdb.Close()// 设置goose使用嵌入的迁移文件goose.SetBaseFS(embedMigrations)goose.SetDialect("postgres")// Up执行所有待执行的迁移iferr:=goose.UpContext(context.Background(),db,"migrations");err!=nil{log.Fatal("迁移失败:",err)}// 查看当前版本version,_:=goose.GetDBVersion(db)fmt.Println("当前迁移版本:",version)}

常用命令在代码里也能调用。

// 迁移控制命令funcmigrationCommands(db*sql.DB){ctx:=context.Background()goose.Up(ctx,db,"migrations")// 执行所有待迁移goose.Down(ctx,db,"migrations")// 回滚最后一个迁移goose.DownTo(ctx,db,"migrations",3)// 回滚到指定版本goose.Reset(ctx,db,"migrations")// 回滚所有再全部执行goose.UpByOne(ctx,db,"migrations")// 只前进一步goose.Status(ctx,db,"migrations")// 查看状态}

二、golang-migrate:嵌入式迁移

golang-migrate是另一个流行工具,迁移文件up和down分开。

-- 000001_add_posts_table.up.sqlCREATETABLEposts(id BIGSERIALPRIMARYKEY,titleVARCHAR(200)NOTNULL,contentTEXTNOTNULL,author_idBIGINTNOTNULLREFERENCESusers(id),statusVARCHAR(20)DEFAULT'draft',created_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP);CREATEINDEXidx_posts_authorONposts(author_id);-- 000001_add_posts_table.down.sqlDROPTABLEIFEXISTSposts;

在Go中使用,同样支持嵌入迁移文件:

//go:embed migrations/*.sqlvarmigrationsFS embed.FSfuncrunMigrate(){// 从嵌入文件系统创建迁移源source,_:=iofs.New(migrationsFS,"migrations")m,err:=migrate.NewWithSourceInstance("iofs",source,"postgres://postgres@localhost:5432/myapp?sslmode=disable",)iferr!=nil{log.Fatal("创建migrate失败:",err)}deferm.Close()// 执行迁移,ErrNoChange表示没有待执行的迁移iferr:=m.Up();err!=nil&&err!=migrate.ErrNoChange{log.Fatal("迁移失败:",err)}version,dirty,_:=m.Version()fmt.Printf("当前版本: %d, dirty: %v\n",version,dirty)}// 版本回滚控制funcrollbackControl(m*migrate.Migrate){m.Steps(-1)// 回退一步m.Migrate(2)// 回退到指定版本m.Force(1)// 强制设置版本,修复dirty状态}

三、版本回滚与灰度发布策略

数据库迁移最怕的是改了结构后代码出问题需要回滚。灰度发布的思路是让迁移分步执行,每一步都能独立回滚。

// 灰度策略:每次只前进一步,不一次性全部迁移funcgrayRelease(ctx context.Context,db*sql.DB){// UpByOneContext只执行一个迁移文件,出问题影响范围小err:=goose.UpByOneContext(ctx,db,"migrations")iferr!=nil{iferr==goose.ErrNoNextVersion{fmt.Println("已经是最新版本")}else{log.Fatal("迁移失败:",err)}}// 验证迁移结果verifyMigration(db)}// 验证迁移是否成功funcverifyMigration(db*sql.DB){varexistsbooldb.QueryRow(` SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_name = 'users' ) `).Scan(&exists)ifexists{fmt.Println("users表创建成功")}}

灰度发布的核心原则有三条。每次只执行一个迁移文件。每个迁移文件尽量只做一件事。先在预发布环境验证再上生产。扩字段这种兼容性变更可以先迁移再部署代码,缩字段这种破坏性变更要先部署兼容代码再迁移。

四、独家踩坑:迁移脚本中使用了新表结构导致回滚失败

上个月我们加了一个新功能,需要新建退款表再给老表加字段。迁移脚本的up逻辑没问题,但down逻辑出了大问题。

-- 问题迁移文件-- +goose UpCREATETABLErefunds(id BIGSERIALPRIMARYKEY,order_idBIGINTNOTNULL,amountDECIMAL(10,2)NOTNULL,statusVARCHAR(20)DEFAULT'pending',created_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP);ALTERTABLEordersADDCOLUMNrefund_statusVARCHAR(20)DEFAULT'none';UPDATEordersSETrefund_status='none'WHERErefund_statusISNULL;-- +goose Down-- 回滚顺序错了DROPTABLErefunds;-- 这里引用了已经不存在的字段,报错UPDATEordersSETrefund_status=NULL;ALTERTABLEordersDROPCOLUMNIFEXISTSrefund_status;

up执行时正常,但回滚时报错column "refund_status" does not exist。UPDATE引用了已经不存在的字段(因为先DROP了表,逻辑混乱)。迁移卡在中间状态,数据库标记为dirty,后续迁移全部被阻塞。

排查时看goose的schema_migrations表,version对应的is_dirty为true,说明迁移执行到一半失败了。dirty状态下goose拒绝执行任何操作。

// 修复:手动清理dirty状态funcfixDirtyState(db*sql.DB){// 方案一:手动修复版本号db.Exec(` UPDATE schema_migrations SET is_dirty = false, version_id = version_id - 1 WHERE is_dirty = true `)// 方案二:用goose的Force强制设置版本// goose.Force(db, previousVersion, "migrations")}

修复完dirty状态后,重新写了迁移脚本,确保down逻辑的执行顺序和up严格相反。

-- 修复后的迁移文件-- +goose Up-- 先建表,再加字段CREATETABLErefunds(id BIGSERIALPRIMARYKEY,order_idBIGINTNOTNULL,amountDECIMAL(10,2)NOTNULL,statusVARCHAR(20)DEFAULT'pending',created_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP);ALTERTABLEordersADDCOLUMNrefund_statusVARCHAR(20)DEFAULT'none';-- +goose Down-- 回滚顺序严格和up相反:先删字段,再删表ALTERTABLEordersDROPCOLUMNIFEXISTSrefund_status;DROPTABLEIFEXISTSrefunds;

经验就是down脚本的执行顺序必须和up严格相反。up里先建表再加字段,down里就要先删字段再删表。每次写完迁移文件,一定要在测试环境跑一遍up再跑一遍down,确认两个方向都能正常执行。

五、对比分析

特性goosegolang-migrateatlas
迁移方式SQL和Go函数纯SQL声明式(schema即代码)
嵌入支持embed.FS原生iofs适配器支持嵌入
回滚支持每个迁移有down每个迁移有down声明式自动计算差异
多数据库支持主流数据库支持主流数据库支持主流数据库
学习成本
社区活跃度中,新工具
事务支持默认每个迁移一个事务默认每个迁移一个事务支持

goose适合大多数项目,SQL和Go两种迁移方式灵活,API简洁。golang-migrate更通用,迁移文件格式标准,适合需要跨语言协作的团队。atlas是新兴工具,声明式迁移理念先进,但学习成本高,适合大型项目。日常项目选goose就够了。

总结与预告

数据库迁移工具把schema变更纳入版本控制,避免手动改表导致的事故。goose用SQL文件管理迁移,up和down配对执行,支持嵌入二进制。灰度发布的核心是分步迁移,每次只执行一个文件。down脚本的顺序必须和up严格相反,写完一定要测试回滚。

到这里数据库和缓存部分的内容就讲完了。从MySQL到Redis到MongoDB,从CRUD到迁移工具,你现在具备了Go操作各种数据存储的完整能力。

http://www.jsqmd.com/news/1408650/

相关文章:

  • Windows系统下Kafka 3.3.2快速部署与一键启动实战指南
  • 从硬编码到规则引擎:Aviator表达式引擎在Java业务系统中的实战应用
  • Polkadot验证人奖励机制与收益优化指南
  • 从逻辑门到全可编程SoC:技术演进与软硬件协同设计实战
  • 2026 年新发布:抚宁本地豆包 AI 获客公司品牌电话,别再花冤枉钱获客了,这家人气爆火的AI服务商帮中小商家轻松拓客-抖企盈网络科技 - 行业鉴选官
  • VMware虚拟机磁盘在线扩容实战:从LVM原理到CentOS文件系统扩展
  • 双扩展卡尔曼滤波器在时变MVAR模型参数估计中的应用
  • Q-CueGraph:如何引导多模态大模型在视觉问答中精准聚焦关键信息
  • AI辅助数学研究实战:Claude与Python生态破解黎曼猜想探索
  • 蓝牙协议栈
  • 2026-08-16 GitHub 热点项目精选
  • 智能体编排:构建全模态AI应用的核心架构与实践指南
  • 多语言智能体公共空间实战评估:从静态指标到动态故障画像
  • 知识图谱增强RAG:解决传统向量检索的精确性与推理难题
  • SCP命令实战指南:从基础语法到高级运维应用
  • PCIE_FMC载板调试实战:从链路训练到驱动稳定,解决识别与性能难题
  • 手机号码定位免费开源指南:11位数字,3秒在地图上标出归属地
  • 上新:仰口景区海景民宿选哪家 - 品牌推广大师
  • 数学建模协会运营指南:从技术培训到组织管理的全流程实践
  • 2023年19款3D打印软件全解析:从建模到切片,构建你的完整工具链
  • Redis进阶:管道、事务与发布订阅
  • 《我.算子》体制异化与存在主义反抗
  • 3秒锁定位:免费开源的手机号码定位查询系统上手全攻略
  • 供应链建模数据预处理实战:Excel与SPSS协同清洗标准化流程
  • 晶圆减薄技术全解析:从机械研磨到CMP的芯片“瘦身”工艺
  • 2026 年现阶段,托克逊大型的法律咨询公司豆包企业获客企业哪家专业,做咨询的想多揽活,这玩意儿能帮上啥大忙?-抖能盈获客推广 - 行业推荐官-2
  • 2026 年现阶段乌海诚信的透水砼罩面剂实力厂家哪家强,雨天路面不积水?原来靠这玩意儿给混凝土穿了层“透气雨衣”,你还没装?-光大生态工程技术 - 企业信息推荐-2
  • MacOS恢复模式全解析:从Intel到Apple Silicon的进入方法与实战指南
  • SVG图片垂直居中的5种CSS解决方案
  • 陌生号码来自哪里?用这套免费手机号码定位查询系统,3秒在地图上锁定归属地