迁移功能模块原理说明
基于1.31 本文档深入解析迁移功能模块的设计原理和核心实现,涵盖数据库结构管理、类型映射、约束管理等核心内容。学习完本文档后,您将能够彻底理解 GORM 迁移系统的工作机制,并能够在实际项目中安全高效地进行数据库迁移。
文档目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 08-migration.md ├── 一、设计原理 │ ├── 1.1 模块定位 │ ├── 1.2 设计目的 │ ├── 1.3 结构安排依据 │ └── 1.4 与其他模块的逻辑关系 │ ├── 二、核心数据结构 │ ├── 2.1 Migrator 接口 (gorm.go:67-111) │ │ ├── 接口定义 │ │ ├── 方法分类说明 │ │ └── 接口设计原理 │ ├── 2.2 ColumnType 接口 (gorm.go:34-48) │ │ ├── 接口定义 │ │ ├── 方法说明 │ │ └── 使用示例 │ ├── 2.3 Index 接口 (gorm.go:50-57) │ │ ├── 接口定义 │ │ └── 使用场景 │ ├── 2.4 TableType 接口 (gorm.go:59-65) │ │ ├── 接口定义 │ │ └── 使用场景 │ ├── 2.5 ViewOption 结构体 (gorm.go:27-32) │ │ ├── 结构体定义 │ │ └── 视图创建配置 │ └── 2.6 Migrator 结构体 (migrator/migrator.go:33-43) │ │ ├── 结构体定义 │ │ └── 配置说明 │ ├── 三、迁移核心实现 │ ├── 3.1 Migrator() 方法 (gorm.go:11-20) │ │ ├── 完整源码 │ │ ├── 执行流程详解 │ │ └── 调用链路图 │ ├── 3.2 AutoMigrate() 方法 (migrator/migrator.go:120-205) │ │ ├── 完整源码 │ │ ├── 执行流程详解 │ │ ├── 增量迁移策略 │ │ └── 流程图 │ ├── 3.3 CreateTable() 方法 (migrator/migrator.go:214-316) │ │ ├── 完整源码 │ │ ├── SQL 生成逻辑 │ │ └── 流程图 │ ├── 3.4 表操作方法 │ │ ├── DropTable() (migrator/migrator.go:318-330) │ │ ├── HasTable() (migrator/migrator.go:332-342) │ │ ├── RenameTable() (migrator/migrator.go:344-370) │ │ └── GetTables() (migrator/migrator.go:207-212) │ ├── 3.5 列操作方法 │ │ ├── AddColumn() (migrator/migrator.go:372-393) │ │ ├── DropColumn() (migrator/migrator.go:395-408) │ │ ├── AlterColumn() (migrator/migrator.go:410-425) │ │ ├── MigrateColumn() (migrator/migrator.go:468-592) │ │ ├── HasColumn() (migrator/migrator.go:427-446) │ │ ├── RenameColumn() (migrator/migrator.go:448-466) │ │ └── ColumnTypes() (migrator/migrator.go:614-641) │ ├── 3.6 索引操作方法 │ │ ├── CreateIndex() (migrator/migrator.go:829-862) │ │ ├── DropIndex() (migrator/migrator.go:864-875) │ │ ├── HasIndex() (migrator/migrator.go:877-895) │ │ ├── RenameIndex() (migrator/migrator.go:897-905) │ │ ├── GetIndexes() (migrator/migrator.go:1025-1028) │ │ └── BuildIndexOptions() (migrator/migrator.go:802-822) │ ├── 3.7 约束操作方法 │ │ ├── CreateConstraint() (migrator/migrator.go:756-770) │ │ ├── DropConstraint() (migrator/migrator.go:772-781) │ │ ├── HasConstraint() (migrator/migrator.go:783-800) │ │ └── GuessConstraintInterfaceAndTable() (migrator/migrator.go:698-754) │ ├── 3.8 视图操作方法 │ │ ├── CreateView() (migrator/migrator.go:655-676) │ │ └── DropView() (migrator/migrator.go:678-681) │ └── 3.9 辅助方法 │ ├── RunWithValue() (migrator/migrator.go:60-75) │ ├── DataTypeOf() (migrator/migrator.go:77-87) │ ├── FullDataTypeOf() (migrator/migrator.go:89-108) │ ├── CurrentTable() (migrator/migrator.go:1017-1023) │ └── ReorderModels() (migrator/migrator.go:913-1015) │ ├── 四、类型映射系统 │ ├── 4.1 Go 类型到数据库类型映射 │ │ ├── 基本类型映射表 │ │ ├── 不同数据库的差异 │ │ └── GORM 标签的影响 │ ├── 4.2 自定义类型映射 │ │ ├── 方式一: GORM 标签 │ │ ├── 方式二: GormDataTypeInterface │ │ └── 方式三: 自定义 Dialector │ └── 4.3 类型比较与迁移 │ ├── 类型比较逻辑 │ ├── 大小比较 │ ├── 精度比较 │ └── 可空性比较 │ ├── 五、实战代码示例 │ ├── 5.1 基础迁移示例 │ │ ├── 创建表 │ │ ├── 修改表结构 │ │ ├── 删除表 │ │ └── 重命名操作 │ ├── 5.2 列操作示例 │ │ ├── 添加列 │ │ ├── 修改列类型 │ │ ├── 删除列 │ │ └── 重命名列 │ ├── 5.3 索引操作示例 │ │ ├── 创建普通索引 │ │ ├── 创建唯一索引 │ │ ├── 创建复合索引 │ │ └── 删除索引 │ ├── 5.4 约束操作示例 │ │ ├── 外键约束 │ │ ├── 唯一约束 │ │ ├── 检查约束 │ │ └── 删除约束 │ ├── 5.5 视图操作示例 │ │ ├── 创建视图 │ │ ├── 替换视图 │ │ └── 删除视图 │ ├── 5.6 完整迁移流程示例 │ │ ├── 初始化迁移 │ │ ├── 版本化迁移 │ │ └── 回滚迁移 │ └── 5.7 复杂场景示例 │ ├── 大数据表迁移 │ ├── 跨数据库迁移 │ └── 生产环境迁移 │ ├── 六、可视化流程图 │ ├── 6.1 AutoMigrate 完整执行流程图 │ ├── 6.2 CreateTable SQL 生成流程图 │ ├── 6.3 MigrateColumn 决策流程图 │ ├── 6.4 ReorderModels 拓扑排序流程图 │ └── 6.5 迁移系统架构图 │ ├── 七、最佳实践与故障排查 │ ├── 7.1 配置最佳实践 │ │ ├── 开发环境配置 │ │ ├── 测试环境配置 │ │ └── 生产环境配置 │ ├── 7.2 迁移策略 │ │ ├── 增量迁移策略 │ │ ├── 版本化迁移策略 │ │ └── 零停机迁移策略 │ ├── 7.3 性能优化 │ │ ├── 批量操作优化 │ │ ├── 索引优化建议 │ │ └── 大表迁移优化 │ ├── 7.4 常见问题与解决方案 │ │ ├── 迁移失败问题 │ │ ├── 类型不匹配问题 │ │ ├── 约束冲突问题 │ │ ├── 索引创建问题 │ │ └── 性能问题 │ └── 7.5 安全建议 │ ├── 备份策略 │ ├── 回滚策略 │ └── 权限控制 │ └── 八、学习验证 ├── 8.1 知识自测 │ ├── 基础题 (10 道) │ ├── 进阶题 (8 道) │ └── 实战题 (5 道) └── 8.2 实践练习 ├── 练习 1: 实现自定义迁移工具 ├── 练习 2: 实现版本化迁移系统 └── 练习 3: 实现跨数据库迁移工具
一、设计原理 1.1 模块定位 在整体架构中的位置
迁移功能模块是 GORM 的数据库结构管理层,负责将 Go 模型自动同步到数据库表结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 ┌─────────────────────────────────────────────────────────────┐ │ Go 模型定义 │ │ type User struct { │ │ ID uint `gorm:"primaryKey"` │ │ Name string `gorm:"size:100;not null"` │ │ Email string `gorm:"uniqueIndex"` │ │ CreatedAt time.Time `gorm:"autoCreateTime"` │ │ UpdatedAt time.Time `gorm:"autoUpdateTime"` │ │ } │ └────────────────────────┬────────────────────────────────────┘ │ Schema 解析 ▼ ┌─────────────────────────────────────────────────────────────┐ │ 迁移管理 (本模块) ★ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ AutoMigrate() - 自动迁移 │ │ │ │ ├─ 解析模型 Schema │ │ │ │ ├─ 对比数据库现有结构 │ │ │ │ ├─ 生成 DDL 语句 │ │ │ │ └─ 执行 CREATE/ALTER/DROP │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ 基础操作: │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │CreateTable│ │AddColumn │ │AlterColumn│ │CreateIndex│ │ │ │ (创建表) │ │ (添加列) │ │ (修改列) │ │ (创建索引)│ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ └────────────────────────┬────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ DDL 语句执行 │ │ ┌────────────────────────────────────────────────────┐ │ │ │ CREATE TABLE users ( │ │ │ │ id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, │ │ │ │ name VARCHAR(100) NOT NULL, │ │ │ │ email VARCHAR(255), │ │ │ │ created_at TIMESTAMP, │ │ │ │ updated_at TIMESTAMP │ │ │ │ ) │ │ │ │ │ │ │ │ CREATE UNIQUE INDEX idx_users_email ON users(email) │ │ │ └────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘
核心价值
迁移功能模块的核心价值在于:
自动化 : 自动将 Go 结构体映射到数据库表结构
类型安全 : 通过反射保证类型映射的正确性
增量更新 : 支持表结构的增量修改而非重建
数据库兼容 : 通过 Dialector 支持多种数据库
1.2 设计目的 问题 1: 如何将 Go 结构体自动映射到数据库表结构?
挑战 : Go 类型与数据库类型存在差异
挑战 : 需要处理字段标签和约束
挑战 : 需要支持不同数据库的方言
解决方案 : Schema 解析 + Dialector 适配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 Go 类型 MySQL 类型 PostgreSQL 类型 string → VARCHAR(255 ) VARCHAR(255 )int → INT INTEGERuint → INT UNSIGNED BIGINTfloat64 → DOUBLE DOUBLE PRECISIONtime.Time → TIMESTAMP TIMESTAMPTZ []byte → BLOB BYTEA bool → TINYINT(1 ) BOOLEANtype Dialector interface { Name() string DataTypeOf(*schema.Field) string } func (d MySQL) DataTypeOf(field *schema.Field) string { switch field.DataType { case schema.String: return fmt.Sprintf("varchar(%d)" , field.Size) case schema.Int: return "int" } }
问题 2: 如何实现增量迁移而不破坏现有数据?
挑战 : 生产环境不能随意删除表
挑战 : 需要检测表和列的存在性
挑战 : 需要生成安全的 DDL 语句
解决方案 : 存在性检查 + 条件 DDL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 type User struct { ID uint Name string Avatar string } db.Exec("DROP TABLE users" ) db.Exec("CREATE TABLE users ..." ) db.AutoMigrate(&User{})
问题 3: 如何处理数据库之间的方言差异?
挑战 : 不同数据库的 SQL 语法不同
挑战 : 类型映射存在差异
挑战 : 约束语法不同
解决方案 : Dialector 抽象层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 MySQL: id BIGINT AUTO_INCREMENT PRIMARY KEY PostgreSQL: id BIGSERIAL PRIMARY KEY SQLite: id INTEGER PRIMARY KEY AUTOINCREMENT MySQL: DEFAULT CURRENT_TIMESTAMP PostgreSQL: DEFAULT CURRENT_TIMESTAMP() SQLite: DEFAULT (datetime('now' )) MySQL: CREATE INDEX idx_name ON table(column) PostgreSQL: CREATE INDEX idx_name ON table(column) SQLite: CREATE INDEX idx_name IF NOT EXISTS ON table(column)
1.3 结构安排依据 3 天学习时间的科学分配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Day 1: 迁移基础 (核心概念) 目标: 理解迁移的基本概念和使用 重点: - AutoMigrate API - Migrator 接口 - 类型映射 Day 2: 高级迁移 (核心机制) 目标: 理解迁移的内部实现 重点: - 增量迁移策略 - 约束管理 - 索引管理 Day 3: 生产实践 (最佳实践) 目标: 掌握生产环境的迁移策略 重点: - 版本化迁移 - 迁移回滚 - 安全迁移
由浅入深的学习路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 第 1 层: 使用层 (如何使用) ├─ AutoMigrate 基本用法 ├─ Migrator API └─ 字段标签配置 第 2 层: 机制层 (如何工作) ├─ Schema 如何解析 ├─ 类型如何映射 └─ DDL 如何生成 第 3 层: 原理层 (为什么这样设计) ├─ 数据库方言抽象 ├─ 增量迁移策略 └─ 迁移安全性
1.4 与其他模块的逻辑关系 依赖关系
依赖 Schema : 需要解析模型的元数据
依赖 Dialector : 需要数据库特定的实现
依赖查询构建 : 执行 DDL 语句
支撑关系
支撑开发效率 : 简化数据库结构管理
支撑数据库兼容 : 屏蔽数据库差异
模块交互图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 迁移模块 ↔ 其他模块: ┌─────────────┐ │ Schema │ → 提供模型元数据 │ 模块 │ - 字段信息 └──────┬──────┘ - 关系信息 │ ▼ ┌─────────────────────────────┐ │ 迁移模块 │ │ ┌───────────────────────┐ │ │ │ AutoMigrate │ │ │ │ CreateTable/AlterTable│ │ │ │ AddColumn/DropColumn │ │ │ │ CreateIndex/DropIndex │ │ │ └───────────────────────┘ │ └──────────┬──────────────────┘ │ ├─→ Dialector (数据库方言) ├─→ 查询构建 (执行 DDL) └─→ 错误处理 (迁移失败)
二、核心数据结构 2.1 Migrator 接口 (gorm.go:67-111) 接口定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 type Migrator interface { AutoMigrate(dst ...interface {}) error CurrentDatabase() string FullDataTypeOf(*schema.Field) clause.Expr GetTypeAliases(databaseTypeName string ) []string CreateTable(dst ...interface {}) error DropTable(dst ...interface {}) error HasTable(dst interface {}) bool RenameTable(oldName, newName interface {}) error GetTables() (tableList []string , err error ) TableType(dst interface {}) (TableType, error ) AddColumn(dst interface {}, field string ) error DropColumn(dst interface {}, field string ) error AlterColumn(dst interface {}, field string ) error MigrateColumn(dst interface {}, field *schema.Field, columnType ColumnType) error MigrateColumnUnique(dst interface {}, field *schema.Field, columnType ColumnType) error HasColumn(dst interface {}, field string ) bool RenameColumn(dst interface {}, oldName, field string ) error ColumnTypes(dst interface {}) ([]ColumnType, error ) CreateView(name string , option ViewOption) error DropView(name string ) error CreateConstraint(dst interface {}, name string ) error DropConstraint(dst interface {}, name string ) error HasConstraint(dst interface {}, name string ) bool CreateIndex(dst interface {}, name string ) error DropIndex(dst interface {}, name string ) error HasIndex(dst interface {}, name string ) bool RenameIndex(dst interface {}, oldName, newName string ) error GetIndexes(dst interface {}) ([]Index, error ) }
方法分类说明 1. 自动迁移
AutoMigrate(): 完全自动迁移,自动检测模型变化并更新数据库结构
2. 数据库操作
CurrentDatabase(): 获取当前数据库名称
FullDataTypeOf(): 获取字段的完整数据类型定义
GetTypeAliases(): 获取数据库类型的别名
3. 表操作
CreateTable(): 创建表
DropTable(): 删除表
HasTable(): 检查表是否存在
RenameTable(): 重命名表
GetTables(): 获取所有表名列表
TableType(): 获取表的类型信息
4. 列操作
AddColumn(): 添加列
DropColumn(): 删除列
AlterColumn(): 修改列定义
MigrateColumn(): 智能迁移列(包含完整类型检查)
MigrateColumnUnique(): 迁移列的 UNIQUE 约束
HasColumn(): 检查列是否存在
RenameColumn(): 重命名列
ColumnTypes(): 获取表的所有列类型信息
5. 视图操作
CreateView(): 创建视图
DropView(): 删除视图
6. 约束操作
CreateConstraint(): 创建约束(外键、唯一、检查等)
DropConstraint(): 删除约束
HasConstraint(): 检查约束是否存在
7. 索引操作
CreateIndex(): 创建索引
DropIndex(): 删除索引
HasIndex(): 检查索引是否存在
RenameIndex(): 重命名索引
GetIndexes(): 获取表的所有索引信息
接口设计原理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 Migrator 接口设计原理: ┌─────────────────────────────────────────────────────────────┐ │ Migrator 接口层次 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 高级 API (便捷操作) │ │ │ │ AutoMigrate() → 一键自动迁移 │ │ │ │ MigrateColumn() → 智能列迁移 │ │ │ └─────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 中级 API (结构操作) │ │ │ │ CreateTable/DropTable │ │ │ │ AddColumn/DropColumn/AlterColumn │ │ │ │ CreateIndex/DropIndex │ │ │ │ CreateConstraint/DropConstraint │ │ │ └─────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 低级 API (检查操作) │ │ │ │ HasTable/HasColumn/HasIndex/HasConstraint │ │ │ │ ColumnTypes/GetIndexes/GetTables │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ 设计原则: │ │ 1. 高级 API 自动化程度高,适合开发环境 │ │ 2. 中级 API 精细控制,适合生产环境 │ │ 3. 低级 API 用于检查和诊断 │ │ │ └─────────────────────────────────────────────────────────────┘
2.2 ColumnType 接口 (gorm.go:34-48) 接口定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 type ColumnType interface { Name() string DatabaseTypeName() string ColumnType() (columnType string , ok bool ) PrimaryKey() (isPrimaryKey bool , ok bool ) AutoIncrement() (isAutoIncrement bool , ok bool ) Length() (length int64 , ok bool ) DecimalSize() (precision int64 , scale int64 , ok bool ) Nullable() (nullable bool , ok bool ) Unique() (unique bool , ok bool ) ScanType() reflect.Type Comment() (value string , ok bool ) DefaultValue() (value string , ok bool ) }
方法说明 基础信息方法
Name(): 返回列名
DatabaseTypeName(): 返回数据库类型名(如 varchar、int、timestamp)
ColumnType(): 返回完整的列类型定义(如 varchar(255)、decimal(10,2))
约束相关方法
PrimaryKey(): 检查是否是主键
AutoIncrement(): 检查是否自增
Nullable(): 检查是否可为空
Unique(): 检查是否唯一
类型特性方法
Length(): 获取字符串类型的长度
DecimalSize(): 获取数值类型的精度和小数位数
ScanType(): 获取对应的 Go 类型
元数据方法
Comment(): 获取列的注释
DefaultValue(): 获取列的默认值
使用示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 columnTypes, err := db.Migrator().ColumnTypes(&User{}) if err != nil { log.Fatal(err) } for _, ct := range columnTypes { fmt.Printf("列名: %s\n" , ct.Name()) fmt.Printf(" 数据库类型: %s\n" , ct.DatabaseTypeName()) if fullType, ok := ct.ColumnType(); ok { fmt.Printf(" 完整类型: %s\n" , fullType) } if length, ok := ct.Length(); ok { fmt.Printf(" 长度: %d\n" , length) } if precision, scale, ok := ct.DecimalSize(); ok { fmt.Printf(" 精度: %d, 小数位: %d\n" , precision, scale) } if nullable, ok := ct.Nullable(); ok { fmt.Printf(" 可为空: %v\n" , nullable) } if pk, ok := ct.PrimaryKey(); ok { fmt.Printf(" 主键: %v\n" , pk) } if defaultValue, ok := ct.DefaultValue(); ok { fmt.Printf(" 默认值: %s\n" , defaultValue) } if comment, ok := ct.Comment(); ok { fmt.Printf(" 注释: %s\n" , comment) } }
2.3 Index 接口 (gorm.go:50-57) 接口定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type Index interface { Table() string Name() string Columns() []string PrimaryKey() (isPrimaryKey bool , ok bool ) Unique() (unique bool , ok bool ) Option() string }
使用场景 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 indexes, err := db.Migrator().GetIndexes(&User{}) if err != nil { log.Printf("获取索引失败: %v" , err) return } for _, idx := range indexes { fmt.Printf("索引名: %s\n" , idx.Name()) fmt.Printf(" 表名: %s\n" , idx.Table()) fmt.Printf(" 列: %v\n" , idx.Columns()) if unique, ok := idx.Unique(); ok { fmt.Printf(" 唯一索引: %v\n" , unique) } if pk, ok := idx.PrimaryKey(); ok { fmt.Printf(" 主键索引: %v\n" , pk) } if option := idx.Option(); option != "" { fmt.Printf(" 选项: %s\n" , option) } }
2.4 TableType 接口 (gorm.go:59-65) 接口定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 type TableType interface { Schema() string Name() string Type() string Comment() (comment string , ok bool ) }
使用场景 1 2 3 4 5 6 7 8 9 10 11 12 13 14 tableType, err := db.Migrator().TableType(&User{}) if err != nil { log.Printf("获取表类型失败: %v" , err) return } fmt.Printf("Schema: %s\n" , tableType.Schema()) fmt.Printf("表名: %s\n" , tableType.Name()) fmt.Printf("类型: %s\n" , tableType.Type()) if comment, ok := tableType.Comment(); ok { fmt.Printf("注释: %s\n" , comment) }
2.5 ViewOption 结构体 (gorm.go:27-32) 结构体定义 1 2 3 4 5 6 type ViewOption struct { Replace bool CheckOption string Query *DB }
视图创建配置 字段说明
Replace: 是否使用 CREATE OR REPLACE VIEW 而非 CREATE VIEW
CheckOption: 可选的检查选项,如 WITH CHECK OPTION
Query: 必需的子查询,用于定义视图的内容
使用示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 q := db.Model(&User{}).Where("age > ?" , 20 ) db.Migrator().CreateView("adult_users" , gorm.ViewOption{Query: q}) q := db.Model(&User{}) db.Migrator().CreateView("users_view" , gorm.ViewOption{ Query: q, Replace: true , CheckOption: "WITH CHECK OPTION" , }) db.Migrator().DropView("adult_users" )
2.6 Migrator 结构体 (migrator/migrator.go:33-43) 结构体定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type Migrator struct { Config } type Config struct { CreateIndexAfterCreateTable bool DB *gorm.DB gorm.Dialector }
配置说明 CreateIndexAfterCreateTable
true: 在创建表之后单独创建索引(默认行为)
false: 在 CREATE TABLE 语句中包含索引定义
DB
Dialector
实现接口 1 var _ gorm.Migrator = (*Migrator)(nil )
这确保了 migrator.Migrator 实现了完整的 gorm.Migrator 接口。
三、迁移核心实现 3.1 Migrator() 方法 (gorm.go:11-20) 完整源码 1 2 3 4 5 6 7 8 9 10 11 12 13 func (db *DB) Migrator() Migrator { tx := db.getInstance() for len (tx.Statement.scopes) > 0 { tx = tx.executeScopes() } return tx.Dialector.Migrator(tx.Session(&Session{})) }
执行流程详解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 db.Migrator() 调用流程: db.Migrator() │ ├─► 1. getInstance() │ └─ 获取 DB 实例的克隆 │ ├─ clone == 1: 创建新 Statement │ └─ clone == 2: 克隆现有 Statement │ ├─► 2. executeScopes() │ └─ 执行所有 pending scopes │ └─ 清空 scopes 列表 │ └─► 3. Dialector.Migrator() └─ 调用方言器的 Migrator() 方法 ├─ MySQL: 返回 MySQL Migrator ├─ PostgreSQL: 返回 PostgreSQL Migrator └─ SQLite: 返回 SQLite Migrator
调用链路图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 用户代码 │ ▼ db.Migrator() │ ├─► db.getInstance() │ └─ 返回 DB 克隆实例 │ ├─► executeScopes() │ └─ 应用所有 scope │ ├─► tx.Session(&Session{}) │ └─ 创建新会话 │ └─► tx.Dialector.Migrator() └─ 返回数据库特定的 Migrator 实现 │ ├─ MySQL Migrator ├─ PostgreSQL Migrator ├─ SQLite Migrator ├─ SQL Server Migrator └─ 其他数据库 Migrator
3.2 AutoMigrate() 方法 (migrator/migrator.go:120-205) 完整源码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 func (m Migrator) AutoMigrate(values ...interface {}) error { for _, value := range m.ReorderModels(values, true ) { queryTx, execTx := m.GetQueryAndExecTx() if !queryTx.Migrator().HasTable(value) { if err := execTx.Migrator().CreateTable(value); err != nil { return err } } else { if err := m.RunWithValue(value, func (stmt *gorm.Statement) error { if stmt.Schema == nil { return errors.New("failed to get schema" ) } columnTypes, err := queryTx.Migrator().ColumnTypes(value) if err != nil { return err } var ( parseIndexes = stmt.Schema.ParseIndexes() parseCheckConstraints = stmt.Schema.ParseCheckConstraints() ) for _, dbName := range stmt.Schema.DBNames { var foundColumn gorm.ColumnType for _, columnType := range columnTypes { if columnType.Name() == dbName { foundColumn = columnType break } } if foundColumn == nil { if err = execTx.Migrator().AddColumn(value, dbName); err != nil { return err } } else { field := stmt.Schema.FieldsByDBName[dbName] if err = execTx.Migrator().MigrateColumn(value, field, foundColumn); err != nil { return err } } } if !m.DB.DisableForeignKeyConstraintWhenMigrating && !m.DB.IgnoreRelationshipsWhenMigrating { for _, rel := range stmt.Schema.Relationships.Relations { if rel.Field.IgnoreMigration { continue } if constraint := rel.ParseConstraint(); constraint != nil && constraint.Schema == stmt.Schema && !queryTx.Migrator().HasConstraint(value, constraint.Name) { if err := execTx.Migrator().CreateConstraint(value, constraint.Name); err != nil { return err } } } } for _, chk := range parseCheckConstraints { if !queryTx.Migrator().HasConstraint(value, chk.Name) { if err := execTx.Migrator().CreateConstraint(value, chk.Name); err != nil { return err } } } for _, idx := range parseIndexes { if !queryTx.Migrator().HasIndex(value, idx.Name) { if err := execTx.Migrator().CreateIndex(value, idx.Name); err != nil { return err } } } return nil }); err != nil { return err } } } return nil }
执行流程详解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 AutoMigrate 完整执行流程: AutoMigrate(values ...) │ ├─► 1. ReorderModels(values, true) │ └─ 按依赖关系排序模型 │ ├─ 分析外键依赖 │ ├─ 分析关联关系 │ └─ 返回拓扑排序后的模型列表 │ └─► 2. 遍历每个模型 │ ├─► GetQueryAndExecTx() │ ├─ queryTx: 用于查询 (检查表/列/索引/约束是否存在) │ └─ execTx: 用于执行 (执行 DDL 语句) │ ├─► 3a. 表不存在 → CreateTable() │ └─ 创建完整的表结构 │ ├─ 创建所有列 │ ├─ 创建主键 │ ├─ 创建外键约束 │ └─ 创建索引 │ └─► 3b. 表存在 → 增量迁移 │ ├─► 3b-1. 获取当前列类型 │ └─ ColumnTypes(value) │ ├─► 3b-2. 遍历所有字段 │ │ │ ├─► 列不存在 → AddColumn() │ │ └─ ALTER TABLE ADD COLUMN │ │ │ └─► 列存在 → MigrateColumn() │ ├─ 比较类型 │ ├─ 比较大小 │ ├─ 比较精度 │ ├─ 比较可空性 │ ├─ 比较默认值 │ └─ 需要时 → AlterColumn() │ ├─► 3b-3. 处理关系约束 │ └─ 遍历 Relationships.Relations │ └─ CreateConstraint() │ ├─► 3b-4. 处理检查约束 │ └─ 遍历 ParseCheckConstraints() │ └─ CreateConstraint() │ └─► 3b-5. 处理索引 └─ 遍历 ParseIndexes() └─ CreateIndex()
增量迁移策略 核心原则
只添加,不删除 : AutoMigrate 只添加缺失的列/索引/约束,不会删除任何东西
智能比较 : 在修改前比较现有定义,避免不必要的 ALTER 操作
安全第一 : 使用查询事务先检查,再用执行事务修改
增量迁移决策树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 对于每个字段: │ ├─ 列不存在? │ ├─ Yes → AddColumn() │ └─ No → 继续 │ ├─ 类型需要修改? │ ├─ Yes → AlterColumn() │ └─ No → 继续 │ ├─ 大小需要修改? │ ├─ Yes → AlterColumn() │ └─ No → 继续 │ ├─ 精度需要修改? │ ├─ Yes → AlterColumn() │ └─ No → 继续 │ ├─ 可空性需要修改? │ ├─ Yes → AlterColumn() │ └─ No → 继续 │ ├─ 默认值需要修改? │ ├─ Yes → AlterColumn() │ └─ No → 继续 │ └─ 注释需要修改? ├─ Yes → AlterColumn() └─ No → 跳过
3.3 CreateTable() 方法 (migrator/migrator.go:214-316) 完整源码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 func (m Migrator) CreateTable(values ...interface {}) error { for _, value := range m.ReorderModels(values, false ) { tx := m.DB.Session(&gorm.Session{}) if err := m.RunWithValue(value, func (stmt *gorm.Statement) (err error ) { if stmt.Schema == nil { return errors.New("failed to get schema" ) } var ( createTableSQL = "CREATE TABLE ? (" values = []interface {}{m.CurrentTable(stmt)} hasPrimaryKeyInDataType bool ) for _, dbName := range stmt.Schema.DBNames { field := stmt.Schema.FieldsByDBName[dbName] if !field.IgnoreMigration { createTableSQL += "? ?" hasPrimaryKeyInDataType = hasPrimaryKeyInDataType || strings.Contains(strings.ToUpper(m.DataTypeOf(field)), "PRIMARY KEY" ) values = append (values, clause.Column{Name: dbName}, m.DB.Migrator().FullDataTypeOf(field)) createTableSQL += "," } } if !hasPrimaryKeyInDataType && len (stmt.Schema.PrimaryFields) > 0 { createTableSQL += "PRIMARY KEY ?," primaryKeys := make ([]interface {}, 0 , len (stmt.Schema.PrimaryFields)) for _, field := range stmt.Schema.PrimaryFields { primaryKeys = append (primaryKeys, clause.Column{Name: field.DBName}) } values = append (values, primaryKeys) } for _, idx := range stmt.Schema.ParseIndexes() { if m.CreateIndexAfterCreateTable { defer func (value interface {}, name string ) { if err == nil { err = tx.Migrator().CreateIndex(value, name) } }(value, idx.Name) } else { if idx.Class != "" { createTableSQL += idx.Class + " " } createTableSQL += "INDEX ? ?" if idx.Comment != "" { createTableSQL += fmt.Sprintf(" COMMENT '%s'" , idx.Comment) } if idx.Option != "" { createTableSQL += " " + idx.Option } createTableSQL += "," values = append (values, clause.Column{Name: idx.Name}, tx.Migrator().(BuildIndexOptionsInterface).BuildIndexOptions(idx.Fields, stmt)) } } if !m.DB.DisableForeignKeyConstraintWhenMigrating && !m.DB.IgnoreRelationshipsWhenMigrating { for _, rel := range stmt.Schema.Relationships.Relations { if rel.Field.IgnoreMigration { continue } if constraint := rel.ParseConstraint(); constraint != nil { if constraint.Schema == stmt.Schema { sql, vars := constraint.Build() createTableSQL += sql + "," values = append (values, vars...) } } } } for _, uni := range stmt.Schema.ParseUniqueConstraints() { createTableSQL += "CONSTRAINT ? UNIQUE (?)," values = append (values, clause.Column{Name: uni.Name}, clause.Expr{SQL: stmt.Quote(uni.Field.DBName)}) } for _, chk := range stmt.Schema.ParseCheckConstraints() { createTableSQL += "CONSTRAINT ? CHECK (?)," values = append (values, clause.Column{Name: chk.Name}, clause.Expr{SQL: chk.Constraint}) } createTableSQL = strings.TrimSuffix(createTableSQL, "," ) createTableSQL += ")" if tableOption, ok := m.DB.Get("gorm:table_options" ); ok { createTableSQL += fmt.Sprint(tableOption) } err = tx.Exec(createTableSQL, values...).Error return err }); err != nil { return err } } return nil }
SQL 生成逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 CreateTable SQL 生成流程: CREATE TABLE ? ( ? ?, -- 列定义 ? ?, -- 列定义 ... PRIMARY KEY ?, -- 主键 (如果需要) INDEX ? ?, -- 索引 (如果 CreateIndexAfterCreateTable = false) CONSTRAINT ? FOREIGN KEY ..., -- 外键约束 CONSTRAINT ? UNIQUE (?), -- 唯一约束 CONSTRAINT ? CHECK (?) -- 检查约束 ) [表选项] 生成的 SQL 示例: CREATE TABLE `users` ( `id` bigint UNSIGNED AUTO_INCREMENT PRIMARY KEY, `name` varchar(100) NOT NULL, `email` varchar(255) UNIQUE, `age` int DEFAULT 0, `created_at` timestamp NULL, `updated_at` timestamp NULL, PRIMARY KEY (`id`), UNIQUE INDEX `idx_users_email` (`email`), INDEX `idx_users_name` (`name`), CONSTRAINT `fk_users_team` FOREIGN KEY (`team_id`) REFERENCES `teams`(`id`) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
3.4 表操作方法 DropTable() (migrator/migrator.go:318-330) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (m Migrator) DropTable(values ...interface {}) error { values = m.ReorderModels(values, false ) for i := len (values) - 1 ; i >= 0 ; i-- { tx := m.DB.Session(&gorm.Session{}) if err := m.RunWithValue(values[i], func (stmt *gorm.Statement) error { return tx.Exec("DROP TABLE IF EXISTS ?" , m.CurrentTable(stmt)).Error }); err != nil { return err } } return nil }
关键点 :
使用 ReorderModels() 按依赖关系排序
反向遍历(从后往前),先删除依赖表,再删除被依赖表
使用 IF EXISTS 避免表不存在时报错
HasTable() (migrator/migrator.go:332-342) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func (m Migrator) HasTable(value interface {}) bool { var count int64 m.RunWithValue(value, func (stmt *gorm.Statement) error { currentDatabase := m.DB.Migrator().CurrentDatabase() return m.DB.Raw( "SELECT count(*) FROM information_schema.tables " + "WHERE table_schema = ? AND table_name = ? AND table_type = ?" , currentDatabase, stmt.Table, "BASE TABLE" , ).Row().Scan(&count) }) return count > 0 }
查询说明 :
查询 information_schema.tables 系统表
过滤条件: 数据库名、表名、表类型为 BASE TABLE
返回 count > 0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 func (m Migrator) RenameTable(oldName, newName interface {}) error { var oldTable, newTable interface {} if v, ok := oldName.(string ); ok { oldTable = clause.Table{Name: v} } else { stmt := &gorm.Statement{DB: m.DB} if err := stmt.Parse(oldName); err == nil { oldTable = m.CurrentTable(stmt) } else { return err } } if v, ok := newName.(string ); ok { newTable = clause.Table{Name: v} } else { stmt := &gorm.Statement{DB: m.DB} if err := stmt.Parse(newName); err == nil { newTable = m.CurrentTable(stmt) } else { return err } } return m.DB.Exec("ALTER TABLE ? RENAME TO ?" , oldTable, newTable).Error }
灵活性 :
oldName 和 newName 可以是字符串或结构体
如果是字符串,直接用作表名
如果是结构体,解析其 Schema 获取表名
3.5 列操作方法 AddColumn() (migrator/migrator.go:372-393) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func (m Migrator) AddColumn(value interface {}, name string ) error { return m.RunWithValue(value, func (stmt *gorm.Statement) error { if stmt.Schema == nil { return errors.New("failed to get schema" ) } f := stmt.Schema.LookUpField(name) if f == nil { return fmt.Errorf("failed to look up field with name: %s" , name) } if !f.IgnoreMigration { return m.DB.Exec( "ALTER TABLE ? ADD ? ?" , m.CurrentTable(stmt), clause.Column{Name: f.DBName}, m.DB.Migrator().FullDataTypeOf(f), ).Error } return nil }) }
DropColumn() (migrator/migrator.go:395-408) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func (m Migrator) DropColumn(value interface {}, name string ) error { return m.RunWithValue(value, func (stmt *gorm.Statement) error { if stmt.Schema != nil { if field := stmt.Schema.LookUpField(name); field != nil { name = field.DBName } } return m.DB.Exec( "ALTER TABLE ? DROP COLUMN ?" , m.CurrentTable(stmt), clause.Column{Name: name}, ).Error }) }
AlterColumn() (migrator/migrator.go:410-425) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func (m Migrator) AlterColumn(value interface {}, field string ) error { return m.RunWithValue(value, func (stmt *gorm.Statement) error { if stmt.Schema != nil { if field := stmt.Schema.LookUpField(field); field != nil { fileType := m.FullDataTypeOf(field) return m.DB.Exec( "ALTER TABLE ? ALTER COLUMN ? TYPE ?" , m.CurrentTable(stmt), clause.Column{Name: field.DBName}, fileType, ).Error } } return fmt.Errorf("failed to look up field with name: %s" , field) }) }
MigrateColumn() (migrator/migrator.go:468-592) 这是最复杂的方法,实现智能列迁移。完整源码太长,这里重点讲解其决策逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 func (m Migrator) MigrateColumn(value interface {}, field *schema.Field, columnType gorm.ColumnType) error { if field.IgnoreMigration { return nil } fullDataType := strings.TrimSpace(strings.ToLower(m.DB.Migrator().FullDataTypeOf(field).SQL)) realDataType := strings.ToLower(columnType.DatabaseTypeName()) var alterColumn bool var isSameType = fullDataType == realDataType if !field.PrimaryKey { if !strings.HasPrefix(fullDataType, realDataType) { aliases := m.DB.Migrator().GetTypeAliases(realDataType) for _, alias := range aliases { if strings.HasPrefix(fullDataType, alias) { isSameType = true break } } if !isSameType { alterColumn = true } } } if !isSameType { if length, ok := columnType.Length(); length != int64 (field.Size) { if length > 0 && field.Size > 0 { alterColumn = true } else { matches2 := regFullDataType.FindAllStringSubmatch(fullDataType, -1 ) if !field.PrimaryKey && (len (matches2) == 1 && matches2[0 ][1 ] != fmt.Sprint(length) && ok) { alterColumn = true } } } } if realDataType == "decimal" || realDataType == "numeric" { if regexp.MustCompile(realDataType+`\(.*\)` ).FindString(fullDataType) != "" { precision, scale, ok := columnType.DecimalSize() if ok { if !strings.HasPrefix(fullDataType, fmt.Sprintf("%s(%d,%d)" , realDataType, precision, scale)) && !strings.HasPrefix(fullDataType, fmt.Sprintf("%s(%d)" , realDataType, precision)) { alterColumn = true } } } } if nullable, ok := columnType.Nullable(); ok && nullable == field.NotNull { if !field.PrimaryKey && !nullable { alterColumn = true } } if !field.PrimaryKey { currentDefaultNotNull := field.HasDefaultValue && (field.DefaultValueInterface != nil || !strings.EqualFold(field.DefaultValue, "NULL" )) dv, dvNotNull := columnType.DefaultValue() if dvNotNull && !currentDefaultNotNull { alterColumn = true } else if !dvNotNull && currentDefaultNotNull { alterColumn = true } else if currentDefaultNotNull || dvNotNull { switch field.GORMDataType { case schema.Time: if !strings.EqualFold(strings.TrimSuffix(dv, "()" ), strings.TrimSuffix(field.DefaultValue, "()" )) { alterColumn = true } case schema.Bool: v1, _ := strconv.ParseBool(dv) v2, _ := strconv.ParseBool(field.DefaultValue) alterColumn = v1 != v2 case schema.String: if dv != field.DefaultValue && dv != strings.Trim(field.DefaultValue, "'\"" ) { alterColumn = true } default : alterColumn = dv != field.DefaultValue } } } if comment, ok := columnType.Comment(); ok && comment != field.Comment { if !field.PrimaryKey { alterColumn = true } } if alterColumn { if err := m.DB.Migrator().AlterColumn(value, field.DBName); err != nil { return err } } if err := m.DB.Migrator().MigrateColumnUnique(value, field, columnType); err != nil { return err } return nil }
检查顺序 :
类型检查
大小检查
精度检查
可空性检查
默认值检查
注释检查
UNIQUE 约束检查
四、类型映射系统 4.1 Go 类型到数据库类型映射 基本类型映射表
Go 类型
GORM 数据类型
MySQL
PostgreSQL
SQLite
SQL Server
int
int
INT
INTEGER
INTEGER
INT
int64
int64
BIGINT
BIGINT
INTEGER
BIGINT
uint
uint
INT UNSIGNED
BIGINT
INTEGER
INT
uint64
uint64
BIGINT UNSIGNED
BIGINT
INTEGER
BIGINT
float32
float
FLOAT
REAL
REAL
REAL
float64
double
DOUBLE
DOUBLE PRECISION
REAL
FLOAT
string
string
VARCHAR(255)
VARCHAR(255)
TEXT
NVARCHAR(255)
[]byte
bytes
BLOB
BYTEA
BLOB
VARBINARY(MAX)
bool
bool
TINYINT(1)
BOOLEAN
BOOLEAN
BIT
time.Time
time
TIMESTAMP
TIMESTAMPTZ
DATETIME
DATETIME2
GORM 标签的影响 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 type User struct { Name string `gorm:"size:100"` Name string `gorm:"size:100;not null"` Status string `gorm:"default:'active'"` Email string `gorm:"unique"` Name string `gorm:"index"` Email string `gorm:"uniqueIndex:idx_email"` Data string `gorm:"type:TEXT"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` Name string `gorm:"comment:用户名称"` }
五、实战代码示例 5.1 基础迁移示例 创建表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package mainimport ( "gorm.io/gorm" "gorm.io/driver/mysql" ) type User struct { ID uint `gorm:"primaryKey"` Name string `gorm:"size:100;not null"` Email string `gorm:"size:255;uniqueIndex"` Age int `gorm:"default:0"` Status string `gorm:"size:20;default:'active'"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` } func main () { dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic ("连接数据库失败" ) } err = db.AutoMigrate(&User{}) if err != nil { panic ("迁移失败" ) } err = db.Migrator().CreateTable(&User{}) if err != nil { panic ("创建表失败" ) } }
修改表结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type User struct { ID uint `gorm:"primaryKey"` Name string `gorm:"size:100;not null"` Avatar string `gorm:"size:500"` } db.AutoMigrate(&User{}) type User struct { Name string `gorm:"size:200"` } db.AutoMigrate(&User{})
删除表 1 2 3 4 5 6 7 8 9 10 11 db.Migrator().DropTable(&User{}) db.Migrator().DropTable(&User{}, &Order{}, &Product{}) db.Migrator().DropTable("users" )
重命名操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 db.Migrator().RenameTable("users" , "accounts" ) db.Migrator().RenameTable(&User{}, "accounts" ) db.Migrator().RenameColumn(&User{}, "name" , "username" ) db.Migrator().RenameIndex(&User{}, "idx_users_name" , "idx_accounts_username" )
5.2 列操作示例 添加列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 err := db.Migrator().AddColumn(&User{}, "avatar" ) if err != nil { log.Fatal(err) } columns := []string {"phone" , "address" , "bio" } for _, col := range columns { if err := db.Migrator().AddColumn(&User{}, col); err != nil { log.Printf("添加列 %s 失败: %v" , col, err) } }
修改列类型 1 2 3 4 5 6 7 8 9 10 11 12 err := db.Migrator().AlterColumn(&User{}, "name" ) if err != nil { log.Fatal(err) } type User struct { Name string `gorm:"size:200;not null;comment:用户名称"` } db.AutoMigrate(&User{})
删除列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 err := db.Migrator().DropColumn(&User{}, "avatar" ) if err != nil { log.Fatal(err) } columns := []string {"phone" , "address" , "bio" } for _, col := range columns { if err := db.Migrator().DropColumn(&User{}, col); err != nil { log.Printf("删除列 %s 失败: %v" , col, err) } }
检查列是否存在 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 hasColumn := db.Migrator().HasColumn(&User{}, "email" ) if !hasColumn { db.Migrator().AddColumn(&User{}, "email" ) } columnTypes, err := db.Migrator().ColumnTypes(&User{}) if err != nil { log.Fatal(err) } for _, ct := range columnTypes { fmt.Printf("列名: %s, 类型: %s\n" , ct.Name(), ct.DatabaseTypeName()) }
5.3 索引操作示例 创建普通索引 1 2 3 4 5 6 7 8 9 10 11 12 type User struct { Name string `gorm:"index:idx_name"` Email string `gorm:"index:idx_email"` Age int `gorm:"index:idx_age"` } db.AutoMigrate(&User{}) db.Migrator().CreateIndex(&User{}, "idx_name" )
创建唯一索引 1 2 3 4 5 6 7 8 9 10 11 type User struct { Email string `gorm:"uniqueIndex:idx_email"` Phone string `gorm:"uniqueIndex"` } db.AutoMigrate(&User{}) db.Migrator().CreateIndex(&User{}, "idx_email" )
创建复合索引 1 2 3 4 5 6 7 8 9 type Order struct { UserID uint `gorm:"index:idx_user_status,priority:1"` Status string `gorm:"index:idx_user_status,priority:2"` CreatedAt time.Time `gorm:"index:idx_user_status,priority:3;sort:desc"` } db.AutoMigrate(&Order{})
删除索引 1 2 3 4 5 6 7 8 9 10 11 err := db.Migrator().DropIndex(&User{}, "idx_name" ) if err != nil { log.Fatal(err) } if db.Migrator().HasIndex(&User{}, "idx_name" ) { db.Migrator().DropIndex(&User{}, "idx_name" ) }
5.4 约束操作示例 外键约束 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type Team struct { ID uint `gorm:"primaryKey"` Name string `gorm:"size:100"` } type User struct { ID uint `gorm:"primaryKey"` Name string `gorm:"size:100"` TeamID uint `gorm:"not null"` Team Team `gorm:"foreignKey:TeamID;references:ID;constraint:OnDelete:CASCADE"` } db.AutoMigrate(&Team{}, &User{})
唯一约束 1 2 3 4 5 6 type User struct { Email string `gorm:"unique"` } db.AutoMigrate(&User{})
检查约束 1 2 3 4 5 6 type User struct { Age int `gorm:"check:age >= 18"` } db.AutoMigrate(&User{})
删除约束 1 2 3 4 5 6 7 8 9 10 11 err := db.Migrator().DropConstraint(&User{}, "fk_users_team" ) if err != nil { log.Fatal(err) } if db.Migrator().HasConstraint(&User{}, "fk_users_team" ) { db.Migrator().DropConstraint(&User{}, "fk_users_team" ) }
5.5 视图操作示例 创建视图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 query := db.Model(&User{}).Where("age > ?" , 18 ) err := db.Migrator().CreateView("adult_users" , gorm.ViewOption{Query: query}) if err != nil { log.Fatal(err) } query = db.Model(&Order{}). Select("user_id, COUNT(*) as order_count, SUM(total) as total_amount" ). Group("user_id" ) err = db.Migrator().CreateView("user_order_stats" , gorm.ViewOption{Query: query})
替换视图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 query := db.Model(&User{}) err := db.Migrator().CreateView("users_view" , gorm.ViewOption{ Query: query, Replace: true , }) err = db.Migrator().CreateView("adult_users" , gorm.ViewOption{ Query: db.Model(&User{}).Where("age > ?" , 18 ), Replace: true , CheckOption: "WITH CHECK OPTION" , })
删除视图 1 2 3 4 5 6 err := db.Migrator().DropView("adult_users" ) if err != nil { log.Fatal(err) }
5.6 完整迁移流程示例 版本化迁移系统 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 package mainimport ( "fmt" "gorm.io/gorm" "log" ) type Migration struct { ID uint `gorm:"primaryKey"` Version string `gorm:"size:20;uniqueIndex"` Name string `gorm:"size:255"` } type MigrationStep struct { Version string Name string Up func (*gorm.DB) error Down func (*gorm.DB) error } type MigrationRunner struct { db *gorm.DB steps []MigrationStep } func NewMigrationRunner (db *gorm.DB, steps []MigrationStep) *MigrationRunner { return &MigrationRunner{db: db, steps: steps} } func (r *MigrationRunner) Up() error { if err := r.db.AutoMigrate(&Migration{}); err != nil { return fmt.Errorf("创建迁移历史表失败: %w" , err) } var executed []string r.db.Model(&Migration{}).Pluck("version" , &executed) executedMap := make (map [string ]bool ) for _, v := range executed { executedMap[v] = true } for _, step := range r.steps { if !executedMap[step.Version] { log.Printf("执行迁移: %s - %s" , step.Version, step.Name) if err := step.Up(r.db); err != nil { return fmt.Errorf("迁移 %s 失败: %w" , step.Version, err) } if err := r.db.Create(&Migration{ Version: step.Version, Name: step.Name, }).Error; err != nil { return fmt.Errorf("记录迁移历史失败: %w" , err) } log.Printf("迁移完成: %s" , step.Version) } } return nil } func (r *MigrationRunner) Down(version string ) error { var step *MigrationStep for i := range r.steps { if r.steps[i].Version == version { step = &r.steps[i] break } } if step == nil { return fmt.Errorf("未找到版本 %s 的迁移" , version) } log.Printf("回滚迁移: %s - %s" , step.Version, step.Name) if err := step.Down(r.db); err != nil { return fmt.Errorf("回滚失败: %w" , err) } if err := r.db.Where("version = ?" , version).Delete(&Migration{}).Error; err != nil { return fmt.Errorf("删除迁移历史失败: %w" , err) } log.Printf("回滚完成: %s" , version) return nil } func main () { dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic ("连接数据库失败" ) } steps := []MigrationStep{ { Version: "20240101_120000" , Name: "create_users_table" , Up: func (db *gorm.DB) error { return db.Migrator().CreateTable(&User{}) }, Down: func (db *gorm.DB) error { return db.Migrator().DropTable(&User{}) }, }, { Version: "20240102_120000" , Name: "add_avatar_to_users" , Up: func (db *gorm.DB) error { return db.Migrator().AddColumn(&User{}, "avatar" ) }, Down: func (db *gorm.DB) error { return db.Migrator().DropColumn(&User{}, "avatar" ) }, }, { Version: "20240103_120000" , Name: "create_orders_table" , Up: func (db *gorm.DB) error { return db.Migrator().CreateTable(&Order{}) }, Down: func (db *gorm.DB) error { return db.Migrator().DropTable(&Order{}) }, }, } runner := NewMigrationRunner(db, steps) if err := runner.Up(); err != nil { log.Fatalf("迁移失败: %v" , err) } }
六、可视化流程图 6.1 AutoMigrate 完整执行流程图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 AutoMigrate(values ...) │ ▼ ┌───────────────────────────────┐ │ ReorderModels(values, true) │ │ 按依赖关系排序模型 │ └───────────┬───────────────────┘ │ ▼ ┌───────────────┐ │ 遍历每个模型 │ └───────┬───────┘ │ ▼ ┌───────────────────────────────┐ │ GetQueryAndExecTx() │ │ queryTx: 用于查询 │ │ execTx: 用于执行 │ └───────────┬───────────────────┘ │ ▼ ┌───────────────┐ │ HasTable()? │ └───┬───────┬───┘ │ │ No Yes │ │ ▼ ▼ ┌──────────┐ ┌───────────────────────────────────┐ │CreateTab │ │ 增量迁移 │ │ le() │ │ ┌─────────────────────────────┐ │ └──────────┘ │ │ ColumnTypes() 获取当前列类型 │ │ │ └─────────────┬───────────────┘ │ │ ▼ │ │ ┌─────────────────────────────┐ │ │ │ 遍历每个字段 │ │ │ └──────┬──────────────────┬────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────┐ ┌──────────┐ │ │ │不存在 │ │ 存在 │ │ │ │AddColumn│ │MigrateCol│ │ │ └─────────┘ │ umn() │ │ │ └──────────┘ │ │ │ │ │ ┌───────────────────────────┘ │ │ ▼ │ │ ┌─────────────────────────────┐ │ │ │ 处理关系约束 │ │ │ │ CreateConstraint() │ │ │ └─────────────────────────────┘ │ │ │ │ ┌─────────────────────────────┐ │ │ │ 处理检查约束 │ │ │ │ CreateConstraint() │ │ │ └─────────────────────────────┘ │ │ │ │ ┌─────────────────────────────┐ │ │ │ 处理索引 │ │ │ │ CreateIndex() │ │ │ └─────────────────────────────┘ │ └───────────────────────────────────┘
6.2 CreateTable SQL 生成流程图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 CreateTable(value) │ ▼ ┌───────────────────────────────────┐ │ RunWithValue(value, func(stmt) {}) │ └───────────────┬───────────────────┘ │ ▼ ┌────────────────────────────────────┐ │ createTableSQL = "CREATE TABLE ? (" │ │ values = [CurrentTable(stmt)] │ └───────────────┬────────────────────┘ │ ▼ ┌────────────────────────────────────┐ │ 遍历 stmt.Schema.DBNames │ │ 添加列定义: "? ?" │ │ values += [列名, FullDataTypeOf] │ └───────────────┬────────────────────┘ │ ▼ ┌────────────────────────────────────┐ │ 如果没有 PRIMARY KEY 在数据类型中 │ │ 添加: "PRIMARY KEY ?" │ │ values += [主键列列表] │ └───────────────┬────────────────────┘ │ ▼ ┌────────────────────────────────────┐ │ 遍历 ParseIndexes() │ │ 如果 CreateIndexAfterCreateTable │ │ → defer CreateIndex() │ │ 否则 │ │ → 添加索引定义到 CREATE TABLE │ └───────────────┬────────────────────┘ │ ▼ ┌────────────────────────────────────┐ │ 如果未禁用外键约束 │ │ 遍历 Relationships.Relations │ │ 添加约束定义到 CREATE TABLE │ └───────────────┬────────────────────┘ │ ▼ ┌────────────────────────────────────┐ │ 遍历 ParseUniqueConstraints() │ │ 添加: "CONSTRAINT ? UNIQUE (?)" │ └───────────────┬────────────────────┘ │ ▼ ┌────────────────────────────────────┐ │ 遍历 ParseCheckConstraints() │ │ 添加: "CONSTRAINT ? CHECK (?)" │ └───────────────┬────────────────────┘ │ ▼ ┌────────────────────────────────────┐ │ 移除末尾逗号 │ │ 添加: ")" │ │ 添加表选项 (如果有的话) │ └───────────────┬────────────────────┘ │ ▼ ┌────────────────────────────────────┐ │ tx.Exec(createTableSQL, values...) │ └────────────────────────────────────┘
6.3 MigrateColumn 决策流程图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 MigrateColumn(value, field, columnType) │ ▼ ┌───────────────────────────────┐ │ field.IgnoreMigration? │ └─────────┬───────────────┬─────┘ │ Yes │ No ▼ ▼ ┌─────────┐ ┌──────────────────┐ │ return │ │ 获取 fullDataType │ │ nil │ │ 和 realDataType │ └─────────┘ └────────┬─────────┘ │ ┌─────────────┴─────────────┐ ▼ ▼ ┌───────────────────┐ ┌──────────────────┐ │ 检查类型 │ │ 检查大小 │ │ alterColumn = │ │ alterColumn = │ │ 类型不匹配 │ │ 大小不匹配 │ └─────────┬─────────┘ └────────┬─────────┘ │ │ ┌─────────┴─────────┐ │ ▼ ▼ ▼ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │ 检查精度 │ │ 检查可空性 │ │ 检查默认值 │ │ alterColumn │ │ alterColumn │ │ alterColumn │ │ │ │ │ │ │ └──────┬──────┘ └──────┬──────┘ └──────┬───────┘ │ │ │ └─────────────────┴─────────────────┘ │ ▼ ┌──────────────────────┐ │ 检查注释 │ │ alterColumn = │ │ 注释不匹配 │ └──────────┬───────────┘ │ ▼ ┌──────────────────────┐ │ alterColumn? │ └──────────┬───────────┘ │ ┌────────┴────────┐ │ │ Yes No │ │ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ AlterColumn()│ │ 跳过 │ └──────┬───────┘ └──────────────┘ │ ▼ ┌──────────────────┐ │ MigrateColumn │ │ Unique() │ └──────────────────┘
七、最佳实践与故障排查 7.1 配置最佳实践 开发环境配置 1 2 3 4 5 6 7 db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ SkipDefaultTransaction: false , DisableForeignKeyConstraintWhenMigrating: false , IgnoreRelationshipsWhenMigrating: false , Logger: logger.Default.LogMode(logger.Info), })
生产环境配置 1 2 3 4 5 6 7 8 9 10 11 12 db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ SkipDefaultTransaction: true , DisableForeignKeyConstraintWhenMigrating: true , Logger: logger.Default.LogMode(logger.Silent), }) runner := NewMigrationRunner(db, migrations) if err := runner.Up(); err != nil { log.Fatal("迁移失败:" , err) }
7.2 常见问题与解决方案 迁移失败问题 1 2 3 4 5 6 db.Set("gorm:table_options" , "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" ).AutoMigrate(&User{})
类型不匹配问题 1 2 3 4 5 6 7 8 type User struct { Status string `gorm:"type:varchar(20);default:'active'"` }
约束冲突问题 1 2 3 4 5 6 db.AutoMigrate(&Team{}, &User{})
八、学习验证 8.1 知识自测 基础题
以下哪个方法用于自动迁移模型?
A. db.Create()
B. db.AutoMigrate()
C. db.Migrate()
D. db.Update()
答案: B
AutoMigrate 的增量迁移策略包括哪些操作?
A. 只添加缺失的列
B. 只修改不匹配的列
C. 删除多余的列
D. A 和 B
答案: D
以下哪个 GORM 标签用于创建唯一索引?
A. index
B. uniqueIndex
C. unique
D. B 和 C
答案: D
如何检查一个表是否存在?
A. db.Table(“users”).First()
B. db.Migrator().HasTable(&User{})
C. db.Exec(“SHOW TABLES”)
D. 以上都可以
答案: B
MigrateColumn() 方法会检查哪些内容? (多选)
A. 列类型
B. 列大小
C. 可空性
D. 默认值
E. 注释
答案: A, B, C, D, E
进阶题
分析以下代码的执行流程:
1 2 3 4 5 type User struct { ID uint `gorm:"primaryKey"` Name string } db.AutoMigrate(&User{})
答案:
ReorderModels 排序模型
HasTable 检查表是否存在
表不存在,调用 CreateTable
生成 CREATE TABLE SQL
执行 SQL 创建表
如何实现跨数据库的类型映射?
答案:
实现 GormDataTypeInterface 接口
在 Dialector 中实现 DataTypeOf 方法
使用 GORM 标签指定特定类型
实战题
实现一个完整的用户表迁移,包含索引和约束
1 2 3 4 5 6 7 8 9 10 11 12 type User struct { ID uint `gorm:"primaryKey"` Username string `gorm:"size:50;not null;uniqueIndex"` Email string `gorm:"size:100;not null;uniqueIndex"` Age int `gorm:"check:age >= 18"` Status string `gorm:"size:20;default:'active'"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` DeletedAt gorm.DeletedAt `gorm:"index"` } db.AutoMigrate(&User{})
8.2 实践练习 练习 1: 实现自定义迁移工具 创建一个支持回滚的迁移工具,要求:
支持版本化迁移
支持迁移历史记录
支持回滚到指定版本
验收标准:
能够正确执行迁移
能够记录迁移历史
能够正确回滚迁移
二、核心原理 2.1 关键概念 概念 2: AutoMigrate 实现 定义 : AutoMigrate 是自动迁移的核心方法。
实现流程 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 AutoMigrate(dst ...interface{}) error: │ ├─► 1. 解析所有模型的 Schema │ └─ db.Statement.Parse(dst) │ ├─ 提取字段信息 │ ├─ 提取关系信息 │ └─ 构建元数据 │ ├─► 2. 遍历每个模型 │ └─ for _, value := range dst │ ├─ 获取表名 │ ├─ 检查表是否存在 │ │ │ ├─► 2a. 表不存在 → CreateTable() │ │ └─ CREATE TABLE ... │ │ │ └─► 2b. 表存在 → 增量更新 │ ├─ 遍历所有字段 │ │ ├─ 字段不存在 → AddColumn() │ │ ├─ 字段类型不同 → AlterColumn() │ │ └─ 字段存在 → 跳过 │ │ │ ├─ 处理关系 (外键) │ │ └─ CreateConstraint() │ │ │ └─ 处理索引 │ └─ CreateIndex() │ └─► 3. 返回错误
关键代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 func (m Migrator) AutoMigrate(dst ...interface {}) error { for _, value := range dst { tx := m.DB.Session(&gorm.Session{SkipDefaultTransaction: true }) if err := tx.Statement.Parse(value); err != nil { return err } if !m.HasTable(value) { if err := m.CreateTable(value); err != nil { return err } } else { for _, field := range tx.Statement.Schema.Fields { if !m.HasColumn(value, field.DBName) { if err := m.AddColumn(value, field.DBName); err != nil { return err } } else { columnTypes, _ := m.ColumnTypes(value) for _, columnType := range columnTypes { if columnType.Name() == field.DBName { if m.DB.Dialector.DataTypeOf(field) != columnType.DatabaseTypeName() { m.AlterColumn(value, field.DBName) } } } } } } for _, rel := range tx.Statement.Schema.Relationships.Relations { if rel.Type == schema.HasMany || rel.Type == schema.Many2Many { if !m.HasTable(rel.JoinTable) { m.CreateTable(rel.JoinTable) } } else { if !m.HasConstraint(value, rel.Name) { m.CreateConstraint(value, rel.Name) } } } for _, index := range tx.Statement.Schema.ParseIndexes() { if !m.HasIndex(value, index.Name) { m.CreateIndex(value, index.Name) } } } return nil }
概念 3: 类型映射系统 定义 : 类型映射将 Go 类型转换为数据库特定的 SQL 类型。
类型映射表 :
Go 类型
MySQL
PostgreSQL
SQLite
说明
int
INT
INTEGER
INTEGER
有符号整数
uint
INT UNSIGNED
BIGINT
INTEGER
无符号整数
int64
BIGINT
BIGINT
INTEGER
大整数
uint64
BIGINT UNSIGNED
BIGINT
INTEGER
大整数
float32
FLOAT
REAL
REAL
浮点数
float64
DOUBLE
DOUBLE PRECISION
REAL
双精度
string
VARCHAR(255)
VARCHAR(255)
TEXT
可变长字符串
[]byte
BLOB
BYTEA
BLOB
二进制数据
bool
TINYINT(1)
BOOLEAN
BOOLEAN
布尔值
time.Time
TIMESTAMP
TIMESTAMPTZ
DATETIME
时间戳
GORM 标签影响 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Name string `gorm:"size:100"` → VARCHAR(100 ) Name string `gorm:"not null"` → VARCHAR(255 ) NOT NULL Status string `gorm:"default:'active'"` → VARCHAR(255 ) DEFAULT 'active' Email string `gorm:"unique"` → VARCHAR(255 ) UNIQUE Name string `gorm:"index"` → 创建索引 idx_<table>_name Email string `gorm:"uniqueIndex"` → 创建唯一索引 CreatedAt time.Time `gorm:"autoCreateTime"` → TIMESTAMP DEFAULT CURRENT_TIMESTAMP UpdatedAt time.Time `gorm:"autoUpdateTime"` → TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
自定义类型映射 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type CustomType string type User struct { Data CustomType `gorm:"type:varchar(500)"` } func (CustomType) GormDataType() string { return "varchar(500)" } func (d CustomDialector) DataTypeOf(field *schema.Field) string { if field.DataType == "customtype" { return "TEXT" } return d.Dialector.DataTypeOf(field) }
概念 4: 约束管理 定义 : 约束管理处理主键、外键、唯一约束等。
主键约束 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type User struct { ID uint `gorm:"primaryKey"` } type OrderItem struct { OrderID uint `gorm:"primaryKey"` ProductID uint `gorm:"primaryKey"` }
外键约束 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type User struct { ID uint Name string } type Order struct { ID uint UserID uint `gorm:"not null"` User User `gorm:"foreignKey:UserID;references:ID"` }
唯一约束 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type User struct { Email string `gorm:"unique"` } type User struct { FirstName string LastName string } db.Migrator().CreateIndex(&User{}, "idx_users_name" )
概念 5: 索引管理 定义 : 索引管理处理普通索引和唯一索引的创建和删除。
索引类型 :
1 2 3 4 5 6 type User struct { ID uint Name string `gorm:"index"` Email string `gorm:"uniqueIndex"` Age int `gorm:"index:idx_age"` }
索引策略 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 索引选择原则: 适合创建索引的列: ┌────────────────────────────────────┐ │ 1. 经常用于 WHERE 查询的列 │ │ 2. 经常用于 JOIN 连接的列 │ │ 3. 经常用于 ORDER BY 的列 │ │ 4. 唯一性约束列 │ └────────────────────────────────────┘ 不适合创建索引的列: ┌────────────────────────────────────┐ │ 1. 频繁更新的列 │ │ 2. 区分度低的列 (如性别) │ │ 3. BLOB/TEXT 大文本列 │ │ 4. 记录很少的表 │ └────────────────────────────────────┘
复合索引 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Order struct { UserID uint ProductID uint Status string CreatedAt time.Time } db.Migrator().CreateIndex(&Order{}, "idx_user_product_status" )
2.2 理论基础 理论 1: 增量迁移策略 增量迁移 vs 完全重建 :
策略
优点
缺点
适用场景
增量迁移
保留数据、安全
复杂、易出错
生产环境
完全重建
简单、一致
丢失数据、停机
开发环境
增量迁移实现 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 func SafeMigrate (db *gorm.DB) error { if !db.Migrator().HasTable(&User{}) { if err := db.Migrator().CreateTable(&User{}); err != nil { return err } } if !db.Migrator().HasColumn(&User{}, "avatar" ) { if err := db.Migrator().AddColumn(&User{}, "avatar" ); err != nil { return err } } columnTypes, _ := db.Migrator().ColumnTypes(&User{}) for _, ct := range columnTypes { if ct.Name() == "name" && ct.DatabaseTypeName() != "varchar(100)" { if err := db.Migrator().AlterColumn(&User{}, "name" ); err != nil { return err } } } if !db.Migrator().HasIndex(&User{}, "idx_users_email" ) { if err := db.Migrator().CreateIndex(&User{}, "idx_users_email" ); err != nil { return err } } return nil }
理论 2: 版本化迁移 定义 : 版本化迁移为每次迁移分配版本号,支持回滚。
迁移模型 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 type Migration struct { Version string Name string Up func (*gorm.DB) error Down func (*gorm.DB) error } var migrations = []Migration{ { Version: "20240101_120000" , Name: "create_users_table" , Up: func (db *gorm.DB) error { return db.Migrator().CreateTable(&User{}) }, Down: func (db *gorm.DB) error { return db.Migrator().DropTable(&User{}) }, }, { Version: "20240102_120000" , Name: "add_avatar_to_users" , Up: func (db *gorm.DB) error { return db.Migrator().AddColumn(&User{}, "avatar" ) }, Down: func (db *gorm.DB) error { return db.Migrator().DropColumn(&User{}, "avatar" ) }, }, }
迁移执行 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 func RunMigrations (db *gorm.DB) error { db.Migrator().AutoMigrate(&MigrationHistory{}) var executed []string db.Model(&MigrationHistory{}).Pluck("version" , &executed) for _, migration := range migrations { if !contains(executed, migration.Version) { if err := migration.Up(db); err != nil { return err } db.Create(&MigrationHistory{ Version: migration.Version, Name: migration.Name, }) } } return nil } func RollbackMigration (db *gorm.DB, version string ) error { var migration Migration for _, m := range migrations { if m.Version == version { migration = m break } } if err := migration.Down(db); err != nil { return err } db.Where("version = ?" , version).Delete(&MigrationHistory{}) return nil }
理论 3: 数据库兼容性 方言差异处理 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 func (m MySQL) CreateTable(dst ...interface {}) error { return m.RunWithValue(dst, "" , func (stmt *Statement) error { for _, column := range stmt.Schema.Fields { if column.PrimaryKey { sql += " AUTO_INCREMENT" } } return nil }) } func (m Postgres) CreateTable(dst ...interface {}) error { return m.RunWithValue(dst, "" , func (stmt *Statement) error { for _, column := range stmt.Schema.Fields { if column.PrimaryKey { sql += " SERIAL" } } return nil }) } func (m SQLite) CreateTable(dst ...interface {}) error { return m.RunWithValue(dst, "" , func (stmt *Statement) error { for _, column := range stmt.Schema.Fields { if column.PrimaryKey { sql += " PRIMARY KEY AUTOINCREMENT" } } return nil }) }
2.3 学习方法 方法 1: 对比法 对比不同迁移方式 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 db.AutoMigrate(&User{}, &Order{}, &Product{}) RunMigrations(db)
方法 2: 实验法 实验 1: 观察迁移 SQL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Info), }) db.AutoMigrate(&User{})
实验 2: 测试增量迁移
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 type User struct { ID uint Name string } db.AutoMigrate(&User{}) type User struct { ID uint Name string Avatar string } db.AutoMigrate(&User{})
2.4 实施策略 策略 1: 分层学习 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 第 1 层: 理解基本概念 (Day 1) 目标: 理解迁移的基本概念 内容: - AutoMigrate API - Migrator 接口 - 类型映射 验证: - 能使用 AutoMigrate - 能理解类型映射 - 能配置字段标签 第 2 层: 掌握高级迁移 (Day 2) 目标: 理解迁移的内部实现 内容: - 增量迁移策略 - 约束管理 - 索引管理 验证: - 能实现增量迁移 - 能管理约束 - 能优化索引 第 3 层: 应用最佳实践 (Day 3) 目标: 掌握生产环境迁移 内容: - 版本化迁移 - 迁移回滚 - 安全迁移 验证: - 能实现版本化迁移 - 能安全回滚 - 能规划迁移策略
策略 2: 场景驱动 场景序列 :
开发环境快速迭代
使用 AutoMigrate
允许删除重建
优先开发速度
生产环境零停机
大数据表迁移
三、学习路径建议 3.1 前置知识检查
知识点
要求
检验方式
SQL DDL
理解 CREATE/ALTER/DROP
能写出基本的 DDL 语句
数据库类型
了解基本数据类型
能对比 VARCHAR 和 TEXT
索引原理
理解索引的作用
能解释何时使用索引
约束概念
了解主键、外键
能解释约束的作用
3.2 学习时间分配
内容
理论
实践
产出
Day 1: 迁移基础
2h
1.5h
AutoMigrate 示例
Day 2: 高级迁移
1.5h
2h
约束和索引示例
Day 3: 生产实践
1.5h
2h
版本化迁移系统
3.3 学习成果验收 理论验收 :
实践验收 :
综合验收 :