数据库迁移: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(contextdatabase/sqlembedfmtloggithub.com/pressly/goose/v3_github.com/lib/pq)// 把migrations目录下的SQL文件嵌入到二进制中////go:embed migrations/*.sqlvarembedMigrations embed.FSfuncmain(){// 连接数据库db,err:sql.Open(postgres,hostlocalhost port5432 userpostgres dbnamemyapp sslmodedisable)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)DEFAULTdraft,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://postgreslocalhost:5432/myapp?sslmodedisable,)iferr!nil{log.Fatal(创建migrate失败:,err)}deferm.Close()// 执行迁移ErrNoChange表示没有待执行的迁移iferr:m.Up();err!nilerr!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{iferrgoose.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)DEFAULTpending,created_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP);ALTERTABLEordersADDCOLUMNrefund_statusVARCHAR(20)DEFAULTnone;UPDATEordersSETrefund_statusnoneWHERErefund_statusISNULL;-- goose Down-- 回滚顺序错了DROPTABLErefunds;-- 这里引用了已经不存在的字段报错UPDATEordersSETrefund_statusNULL;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)DEFAULTpending,created_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP);ALTERTABLEordersADDCOLUMNrefund_statusVARCHAR(20)DEFAULTnone;-- 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操作各种数据存储的完整能力。

相关新闻

从Scratch图形化编程到计算思维:以“接苹果”游戏为例的工程实践

从Scratch图形化编程到计算思维:以“接苹果”游戏为例的工程实践

在实际编程教育和技术社区中,我们常常会遇到一个有趣的现象:一些经验丰富的开发者或计算机专业的学生,初次接触 Scratch 这类图形化编程工具时,会下意识地产生一种“这太简单了”或“这不是真正的编程”的想法,甚至可能…

2026/10/18 16:50:20 阅读更多 →
陌生号码来自哪里?用这套免费手机号码定位查询系统,3秒在地图上锁定归属地

陌生号码来自哪里?用这套免费手机号码定位查询系统,3秒在地图上锁定归属地

陌生号码来自哪里?用这套免费手机号码定位查询系统,3秒在地图上锁定归属地 【免费下载链接】location-to-phone-number This a project to search a location of a specified phone number, and locate the map to the phone number location. 项目地址…

2026/10/18 16:51:01 阅读更多 →
手机号码定位免费开源指南:11位数字,3秒在地图上标出归属地

手机号码定位免费开源指南:11位数字,3秒在地图上标出归属地

手机号码定位免费开源指南:11位数字,3秒在地图上标出归属地 【免费下载链接】location-to-phone-number This a project to search a location of a specified phone number, and locate the map to the phone number location. 项目地址: https://git…

2026/10/17 6:45:20 阅读更多 →

最新新闻

基于YOLO的口罩佩戴检测系统毕设实战指南

基于YOLO的口罩佩戴检测系统毕设实战指南

毕设选题这件事,每年都有一批人卡在同一个地方:想做个“有技术含量、工作量足、还能拿得出手”的题目,又担心难度太大自己搞不定。如果你刚好是计算机视觉方向,我强烈建议你看一眼“深度学习口罩佩戴检测系统”这类题目。它不只是…

2026/10/18 16:50:46 阅读更多 →
Eclipse中创建Maven工程:从环境配置到项目运行的完整指南

Eclipse中创建Maven工程:从环境配置到项目运行的完整指南

1. 为什么在IDE里手动搭Maven工程比命令行更值得花时间很多人第一次接触Maven,习惯直接在终端敲mvn archetype:generate,一路回车把骨架拉起来,然后才用IDE打开。这个流程本身没毛病,但如果你后续要长期维护这个工程,我…

2026/10/18 16:50:45 阅读更多 →
自动驾驶入门硬件选型与实车调试实战指南

自动驾驶入门硬件选型与实车调试实战指南

1. 这不是教科书,而是一份“能上手、不踩坑、真能跑通”的自动驾驶入门实录“自动驾驶入门指南”这六个字,最近在技术社区、高校实验室甚至职业培训平台高频出现。但你点开十篇所谓“入门指南”,八篇在讲L0-L5分级、三篇贴出ROS安装命令、两篇…

2026/10/18 16:50:45 阅读更多 →
CMake静态库与动态库生成链接实战指南

CMake静态库与动态库生成链接实战指南

1. 从一个真实困境说起:为什么你的CMake项目总是链接失败如果你写过C,大概率经历过这样的场景:代码明明编译通过了,一到链接阶段就报一堆undefined reference to ...,或者运行时提示error while loading shared librar…

2026/10/18 16:50:45 阅读更多 →
Claude Code 知识工作插件与 slash commands 实战指南

Claude Code 知识工作插件与 slash commands 实战指南

1. 从零认识 knowledge-work-plugins:它到底解决什么问题第一次看到knowledge-work-plugins这个仓库名,很多人会以为是某个知识管理软件的插件合集,或者像 Obsidian、Logseq 那种笔记工具的扩展包。实际上它的定位要更“底层”一些——这是一…

2026/10/18 16:50:45 阅读更多 →
零基础也能创建AI智能体:三步搭建个人助手实战指南

零基础也能创建AI智能体:三步搭建个人助手实战指南

身边很多朋友一听到“AI智能体”这个词,第一反应都是:这玩意儿是不是得先学三个月Python,再搞懂什么API调用、微调模型,才配碰一碰?其实真不是。我刚开始接触的时候也抱着同样的刻板印象,直到我自己用纯聊天…

2026/10/18 16:49:44 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →