1. GORM 迁移概述

1.1 什么是数据库迁移

数据库迁移(Database Migration)是一种版本化、可逆的数据库结构变更管理方式。它允许团队像管理代码一样管理表结构、索引、约束的演进,确保开发、测试、生产环境的数据库始终处于一致的状态。

传统手动执行 SQL 脚本的方式存在三个核心痛点:

  • 环境漂移:开发人员本地修改了表结构,但忘记同步给测试或生产环境
  • 变更不可追溯:谁在什么时间改了哪个字段,没有版本记录
  • 回滚困难:上线后发现结构变更导致故障,无法快速恢复到上一个版本

数据库迁移工具通过增量脚本(Up / Down)来解决上述问题。每次变更对应一个迁移文件,按时间戳或序号排序执行,并记录已应用的迁移版本。

对比维度 手动 SQL 脚本 数据库迁移工具
版本管理 无,依赖人工记忆 自动记录已执行版本
回滚能力 需手动编写逆向 SQL 内置 Down 脚本支持
团队协作 容易冲突、遗漏 按顺序合并,可追溯
环境一致性 难以保证 全环境统一执行

1.2 GORM AutoMigrate 简介

GORM 提供了 AutoMigrate 方法,能够根据 Go 结构体定义自动创建或更新数据库表。它属于声明式迁移——你只需定义模型(Model),GORM 自动推导出对应的 DDL 语句。

核心用法:

// 自动迁移:根据 User 结构体创建或更新 users 表
db.AutoMigrate(&User{})

// 同时迁移多个模型
db.AutoMigrate(&User{}, &Order{}, &Product{})

AutoMigrate 支持的操作包括:

  • 创建新表
  • 添加新字段
  • 修改字段类型(部分数据库支持)
  • 添加索引和唯一约束
  • 添加外键约束

⚠️ 注意:AutoMigrate 不会删除已存在的字段或表,也不会修改字段的 NULL / NOT NULL 属性(部分数据库)。对于破坏性变更,需要手动编写迁移脚本。

AutoMigrate 的工作流程可以用下图表示:

GORM AutoMigrate 工作流程 Go 结构体定义 type User struct { ... } 结构体解析 标签 / 类型 / 关系 差异对比 模型 vs 实际表 DDL 执行 ALTER TABLE / CREATE 迁移记录 写入迁移版本表 💡 说明: AutoMigrate 只做增量变更,不会删除字段或表。破坏性迁移需手动编写脚本。

1.3 迁移与领域驱动设计的关系

在领域驱动设计(DDD)中,数据库表结构是领域模型的持久化映射。迁移工具承担了“模型契约”的版本管理角色——每次领域模型的调整,都对应一次数据库迁移。

这种关系体现在三个层面:

DDD 概念 迁移中的对应 说明
聚合(Aggregate) 主表 + 关联表 一个聚合对应一组迁移文件,保证数据一致性边界
值对象(Value Object) 嵌入字段 / JSON 列 值对象通常不单独建表,迁移时需处理序列化
领域事件(Domain Event) 事件表 / 日志表 事件溯源场景下,迁移需保证事件表结构稳定
限界上下文(Bounded Context) 独立数据库 / Schema 不同上下文使用独立的迁移序列,避免耦合

💡 最佳实践:将迁移文件按限界上下文组织目录,每个上下文拥有独立的迁移序列。这样当某个上下文重构时,不会影响其他上下文的数据库结构。

下图展示了 DDD 限界上下文与数据库迁移的映射关系:

限界上下文与数据库迁移映射 订单上下文 orders 表 order_items 表 迁移: 001 ~ 015 支付上下文 payments 表 refunds 表 迁移: 001 ~ 008 库存上下文 products 表 inventory 表 迁移: 001 ~ 012 🔑 关键原则: 每个限界上下文拥有独立的迁移序列,表名不跨上下文引用,降低耦合度。

通过将迁移与领域边界对齐,团队可以实现:

  • 独立演进:每个上下文可以独立修改表结构,不影响其他上下文
  • 契约清晰:迁移文件成为领域模型的“持久化契约”,代码与数据库一一对应
  • 可追溯性:每次领域模型的调整,都能在迁移历史中找到对应记录