迁移功能模块原理说明

基于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) │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

核心价值

迁移功能模块的核心价值在于:

  1. 自动化: 自动将 Go 结构体映射到数据库表结构
  2. 类型安全: 通过反射保证类型映射的正确性
  3. 增量更新: 支持表结构的增量修改而非重建
  4. 数据库兼容: 通过 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 INTEGER
uint → INT UNSIGNED BIGINT
float64 → DOUBLE DOUBLE PRECISION
time.Time → TIMESTAMP TIMESTAMPTZ
[]byte → BLOB BYTEA
bool → TINYINT(1) BOOLEAN

// 解决方案: Dialector 接口
type Dialector interface {
Name() string
DataTypeOf(*schema.Field) string // 类型映射
// ...
}

// MySQL 实现
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{})
// 实际执行:
// 1. 检查 users 表是否存在
// 2. 如果不存在,创建表
// 3. 如果存在,检查每个字段
// 4. 只添加 avatar 字段: ALTER TABLE users ADD COLUMN avatar VARCHAR(255)

问题 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
// Migrator migrator interface
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
// ColumnType column type interface
type ColumnType interface {
// 获取列名
Name() string

// 获取数据库类型名称 (如: varchar)
DatabaseTypeName() string

// 获取完整列类型 (如: varchar(64))
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)

// 获取 Go 扫描类型
ScanType() reflect.Type

// 获取注释
Comment() (value string, ok bool)

// 获取默认值
DefaultValue() (value string, ok bool)
}

方法说明

基础信息方法

  • Name(): 返回列名
  • DatabaseTypeName(): 返回数据库类型名(如 varcharinttimestamp
  • 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
// Index 索引接口
type Index interface {
// 获取索引所属的表名
Table() string

// 获取索引名称
Name() string

// 获取索引包含的列名列表
Columns() []string

// 检查是否是主键索引
PrimaryKey() (isPrimaryKey bool, ok bool)

// 检查是否是唯一索引
Unique() (unique bool, ok bool)

// 获取索引选项 (如: WHERE 子句、索引类型等)
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
// TableType table type interface
type TableType interface {
// 获取 Schema 名称 (如: public, dbo 等)
Schema() string

// 获取表名
Name() string

// 获取表类型 (如: BASE TABLE, VIEW 等)
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
// ViewOption view option
type ViewOption struct {
Replace bool // If true, exec `CREATE OR REPLACE`. If false, exec `CREATE`
CheckOption string // optional. e.g. `WITH [ CASCADED | LOCAL ] CHECK OPTION`
Query *DB // required subquery.
}

视图创建配置

字段说明

  • 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})
// 生成: CREATE VIEW `adult_users` AS SELECT * FROM `users` WHERE age > 20

// 创建或替换视图
q := db.Model(&User{})
db.Migrator().CreateView("users_view", gorm.ViewOption{
Query: q,
Replace: true,
CheckOption: "WITH CHECK OPTION",
})
// 生成: CREATE OR REPLACE VIEW `users_view` AS SELECT * FROM `users` WITH CHECK OPTION

// 删除视图
db.Migrator().DropView("adult_users")
// 生成: DROP VIEW IF EXISTS `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
// Migrator m struct
type Migrator struct {
Config
}

// Config schema config
type Config struct {
// 是否在创建表之后创建索引
CreateIndexAfterCreateTable bool

// GORM DB 实例
DB *gorm.DB

// 数据库方言器
gorm.Dialector
}

配置说明

CreateIndexAfterCreateTable

  • true: 在创建表之后单独创建索引(默认行为)
  • false: 在 CREATE TABLE 语句中包含索引定义

DB

  • GORM 数据库实例,用于执行 SQL 语句

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
// Migrator returns migrator
func (db *DB) Migrator() Migrator {
// === 获取 DB 实例 ===
tx := db.getInstance()

// === 应用 scopes 到 migrator ===
for len(tx.Statement.scopes) > 0 {
tx = tx.executeScopes()
}

// === 返回方言器的 migrator ===
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
// AutoMigrate auto migrate values
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 {

// 验证 Schema
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()

增量迁移策略

核心原则

  1. 只添加,不删除: AutoMigrate 只添加缺失的列/索引/约束,不会删除任何东西
  2. 智能比较: 在修改前比较现有定义,避免不必要的 ALTER 操作
  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
对于每个字段:

├─ 列不存在?
│ ├─ 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
// CreateTable create table in database for values
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 += "? ?"
// 检查数据类型中是否包含 PRIMARY KEY
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 {
// 在 CREATE TABLE 中定义索引
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)
}

// === 执行 CREATE TABLE ===
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
// DropTable drop table for values
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
// HasTable returns table exists or not for value
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

RenameTable() (migrator/migrator.go:344-370)

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
// RenameTable rename table from oldName to newName
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
}

灵活性:

  • oldNamenewName 可以是字符串或结构体
  • 如果是字符串,直接用作表名
  • 如果是结构体,解析其 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
// AddColumn create `name` column for value
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
// DropColumn drop value's `name` column
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
// AlterColumn alter value's `field` column' type based on schema definition
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
// MigrateColumn migrate column
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

// === 1. 检查类型 ===
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
}
}
}

// === 2. 检查大小 ===
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
}
}
}
}

// === 3. 检查精度 (decimal/numeric) ===
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
}
}
}
}

// === 4. 检查可空性 ===
if nullable, ok := columnType.Nullable(); ok && nullable == field.NotNull {
if !field.PrimaryKey && !nullable {
alterColumn = true
}
}

// === 5. 检查默认值 ===
if !field.PrimaryKey {
currentDefaultNotNull := field.HasDefaultValue && (field.DefaultValueInterface != nil || !strings.EqualFold(field.DefaultValue, "NULL"))
dv, dvNotNull := columnType.DefaultValue()
if dvNotNull && !currentDefaultNotNull {
alterColumn = true // 默认值 → null
} else if !dvNotNull && currentDefaultNotNull {
alterColumn = true // null → 默认值
} 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
}
}
}

// === 6. 检查注释 ===
if comment, ok := columnType.Comment(); ok && comment != field.Comment {
if !field.PrimaryKey {
alterColumn = true
}
}

// === 执行 ALTER COLUMN (如果需要) ===
if alterColumn {
if err := m.DB.Migrator().AlterColumn(value, field.DBName); err != nil {
return err
}
}

// === 处理 UNIQUE 约束 ===
if err := m.DB.Migrator().MigrateColumnUnique(value, field, columnType); err != nil {
return err
}

return nil
}

检查顺序:

  1. 类型检查
  2. 大小检查
  3. 精度检查
  4. 可空性检查
  5. 默认值检查
  6. 注释检查
  7. 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 {
// size: 指定字符串长度
Name string `gorm:"size:100"`
// MySQL: VARCHAR(100)
// PostgreSQL: VARCHAR(100)

// not null: 非空约束
Name string `gorm:"size:100;not null"`
// MySQL: VARCHAR(100) NOT NULL

// default: 默认值
Status string `gorm:"default:'active'"`
// MySQL: VARCHAR(255) DEFAULT 'active'

// unique: 唯一约束
Email string `gorm:"unique"`
// MySQL: VARCHAR(255) UNIQUE

// index: 创建索引
Name string `gorm:"index"`
// 创建索引: idx_users_name

// uniqueIndex: 唯一索引
Email string `gorm:"uniqueIndex:idx_email"`
// 创建唯一索引: idx_email

// type: 指定数据库类型
Data string `gorm:"type:TEXT"`
// MySQL: TEXT

// autoCreateTime: 自动创建时间
CreatedAt time.Time `gorm:"autoCreateTime"`
// MySQL: TIMESTAMP DEFAULT CURRENT_TIMESTAMP

// autoUpdateTime: 自动更新时间
UpdatedAt time.Time `gorm:"autoUpdateTime"`
// MySQL: TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

// comment: 列注释
Name string `gorm:"comment:用户名称"`
// MySQL: VARCHAR(255) 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 main

import (
"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("连接数据库失败")
}

// 方式 1: 使用 AutoMigrate (推荐)
err = db.AutoMigrate(&User{})
if err != nil {
panic("迁移失败")
}

// 方式 2: 使用 Migrator
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"` // 新增字段
}

// 运行 AutoMigrate,会自动添加 avatar 列
db.AutoMigrate(&User{})
// 生成的 SQL: ALTER TABLE `users` ADD COLUMN `avatar` varchar(500)

// 修改字段大小
type User struct {
Name string `gorm:"size:200"` // 从 100 改为 200
}

// 运行 AutoMigrate,会自动修改列类型
db.AutoMigrate(&User{})
// 生成的 SQL: ALTER TABLE `users` ALTER COLUMN `name` TYPE varchar(200)

删除表

1
2
3
4
5
6
7
8
9
10
11
// 方式 1: 删除单个表
db.Migrator().DropTable(&User{})
// 生成的 SQL: DROP TABLE IF EXISTS `users`

// 方式 2: 删除多个表
db.Migrator().DropTable(&User{}, &Order{}, &Product{})
// 依次删除每个表

// 方式 3: 使用表名
db.Migrator().DropTable("users")
// 生成的 SQL: DROP TABLE IF EXISTS `users`

重命名操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 重命名表
db.Migrator().RenameTable("users", "accounts")
// 生成的 SQL: ALTER TABLE `users` RENAME TO `accounts`

// 使用结构体
db.Migrator().RenameTable(&User{}, "accounts")

// 重命名列
db.Migrator().RenameColumn(&User{}, "name", "username")
// 生成的 SQL: ALTER TABLE `users` RENAME COLUMN `name` TO `username`

// 重命名索引
db.Migrator().RenameIndex(&User{}, "idx_users_name", "idx_accounts_username")
// 生成的 SQL: ALTER TABLE `users` RENAME INDEX `idx_users_name` TO `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)
}
// 生成的 SQL: ALTER TABLE `users` ADD COLUMN `avatar` varchar(500)

// 添加多个列
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)
}
// 生成的 SQL: ALTER TABLE `users` ALTER COLUMN `name` TYPE varchar(200)

// 使用 AutoMigrate 自动检测并修改
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)
}
// 生成的 SQL: ALTER TABLE `users` DROP COLUMN `avatar`

// 删除多个列
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
// 方式 1: 使用 GORM 标签 (推荐)
type User struct {
Name string `gorm:"index:idx_name"`
Email string `gorm:"index:idx_email"`
Age int `gorm:"index:idx_age"`
}

db.AutoMigrate(&User{})

// 方式 2: 手动创建索引
db.Migrator().CreateIndex(&User{}, "idx_name")
// 生成的 SQL: CREATE INDEX `idx_name` ON `users`(`name`)

创建唯一索引

1
2
3
4
5
6
7
8
9
10
11
// 方式 1: 使用 GORM 标签
type User struct {
Email string `gorm:"uniqueIndex:idx_email"`
Phone string `gorm:"uniqueIndex"`
}

db.AutoMigrate(&User{})

// 方式 2: 手动创建唯一索引
db.Migrator().CreateIndex(&User{}, "idx_email")
// 生成的 SQL: CREATE UNIQUE INDEX `idx_email` ON `users`(`email`)

创建复合索引

1
2
3
4
5
6
7
8
9
// 使用 GORM 标签创建复合索引
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{})
// 生成的 SQL: CREATE INDEX `idx_user_status` ON `orders`(`user_id`, `status`, `created_at` DESC)

删除索引

1
2
3
4
5
6
7
8
9
10
11
// 删除单个索引
err := db.Migrator().DropIndex(&User{}, "idx_name")
if err != nil {
log.Fatal(err)
}
// 生成的 SQL: DROP INDEX `idx_name` ON `users`

// 检查索引是否存在后再删除
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{})
// 生成的 SQL:
// ALTER TABLE `users` ADD CONSTRAINT `fk_users_team`
// FOREIGN KEY (`team_id`) REFERENCES `teams`(`id`) ON DELETE CASCADE

唯一约束

1
2
3
4
5
6
type User struct {
Email string `gorm:"unique"`
}

db.AutoMigrate(&User{})
// 生成的 SQL: CREATE UNIQUE INDEX `idx_users_email` ON `users`(`email`)

检查约束

1
2
3
4
5
6
type User struct {
Age int `gorm:"check:age >= 18"`
}

db.AutoMigrate(&User{})
// 生成的 SQL: ALTER TABLE `users` ADD CONSTRAINT `chk_users_age` CHECK (`age` >= 18)

删除约束

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)
}
// 生成的 SQL: ALTER TABLE `users` DROP CONSTRAINT `fk_users_team`

// 检查约束是否存在
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)
}
// 生成的 SQL: CREATE VIEW `adult_users` AS SELECT * FROM `users` WHERE age > 18

// 使用聚合函数
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})
// 生成的 SQL:
// CREATE VIEW `user_order_stats` AS
// SELECT user_id, COUNT(*) as order_count, SUM(total) as total_amount
// FROM `orders` GROUP BY user_id

替换视图

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,
})
// 生成的 SQL: CREATE OR REPLACE VIEW `users_view` AS SELECT * FROM `users`

// 带检查选项
err = db.Migrator().CreateView("adult_users", gorm.ViewOption{
Query: db.Model(&User{}).Where("age > ?", 18),
Replace: true,
CheckOption: "WITH CHECK OPTION",
})
// 生成的 SQL:
// CREATE OR REPLACE VIEW `adult_users` AS
// SELECT * FROM `users` WHERE age > 18
// WITH CHECK OPTION

删除视图

1
2
3
4
5
6
// 删除视图
err := db.Migrator().DropView("adult_users")
if err != nil {
log.Fatal(err)
}
// 生成的 SQL: DROP VIEW IF EXISTS `adult_users`

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 main

import (
"fmt"
"gorm.io/gorm"
"log"
)

// Migration 迁移记录
type Migration struct {
ID uint `gorm:"primaryKey"`
Version string `gorm:"size:20;uniqueIndex"`
Name string `gorm:"size:255"`
}

// MigrationStep 迁移步骤
type MigrationStep struct {
Version string
Name string
Up func(*gorm.DB) error
Down func(*gorm.DB) error
}

// MigrationRunner 迁移执行器
type MigrationRunner struct {
db *gorm.DB
steps []MigrationStep
}

func NewMigrationRunner(db *gorm.DB, steps []MigrationStep) *MigrationRunner {
return &MigrationRunner{db: db, steps: steps}
}

// Up 执行所有待执行的迁移
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
}

// Down 回滚指定版本的迁移
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)
}

// 回滚特定版本
// if err := runner.Down("20240102_120000"); 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), // 显示 SQL 日志
})

生产环境配置

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), // 静默模式
})

// 使用版本化迁移而非 AutoMigrate
runner := NewMigrationRunner(db, migrations)
if err := runner.Up(); err != nil {
log.Fatal("迁移失败:", err)
}

7.2 常见问题与解决方案

迁移失败问题

1
2
3
4
5
6
// 问题: AutoMigrate 失败,错误: "Error 1064: You have an error in your SQL syntax"

// 原因: 数据库方言特定语法问题

// 解决方案: 检查表选项设置
db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4").AutoMigrate(&User{})

类型不匹配问题

1
2
3
4
5
6
7
8
// 问题: AutoMigrate 反复修改列类型

// 原因: Go 类型定义与数据库类型不完全匹配

// 解决方案: 使用 GORM 标签明确指定类型
type User struct {
Status string `gorm:"type:varchar(20);default:'active'"`
}

约束冲突问题

1
2
3
4
5
6
// 问题: 创建外键约束失败

// 原因: 引用的表不存在或列不匹配

// 解决方案: 确保先迁移被引用的表
db.AutoMigrate(&Team{}, &User{}) // Team 必须在 User 之前

八、学习验证

8.1 知识自测

基础题

  1. 以下哪个方法用于自动迁移模型?

    • A. db.Create()
    • B. db.AutoMigrate()
    • C. db.Migrate()
    • D. db.Update()

    答案: B

  2. AutoMigrate 的增量迁移策略包括哪些操作?

    • A. 只添加缺失的列
    • B. 只修改不匹配的列
    • C. 删除多余的列
    • D. A 和 B

    答案: D

  3. 以下哪个 GORM 标签用于创建唯一索引?

    • A. index
    • B. uniqueIndex
    • C. unique
    • D. B 和 C

    答案: D

  4. 如何检查一个表是否存在?

    • A. db.Table(“users”).First()
    • B. db.Migrator().HasTable(&User{})
    • C. db.Exec(“SHOW TABLES”)
    • D. 以上都可以

    答案: B

  5. MigrateColumn() 方法会检查哪些内容? (多选)

    • A. 列类型
    • B. 列大小
    • C. 可空性
    • D. 默认值
    • E. 注释

    答案: A, B, C, D, E

进阶题

  1. 分析以下代码的执行流程:

    1
    2
    3
    4
    5
    type User struct {
    ID uint `gorm:"primaryKey"`
    Name string
    }
    db.AutoMigrate(&User{})

    答案:

    1. ReorderModels 排序模型
    2. HasTable 检查表是否存在
    3. 表不存在,调用 CreateTable
    4. 生成 CREATE TABLE SQL
    5. 执行 SQL 创建表
  2. 如何实现跨数据库的类型映射?

    答案:

    • 实现 GormDataTypeInterface 接口
    • 在 Dialector 中实现 DataTypeOf 方法
    • 使用 GORM 标签指定特定类型

实战题

  1. 实现一个完整的用户表迁移,包含索引和约束
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 {
// === 1. 解析 Schema ===
tx := m.DB.Session(&gorm.Session{SkipDefaultTransaction: true})
if err := tx.Statement.Parse(value); err != nil {
return err
}

// === 2. 创建或更新表 ===
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)
}
}
}
}
}
}

// === 3. 处理关系 ===
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)
}
}
}

// === 4. 处理索引 ===
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
// size: 指定字符串长度
Name string `gorm:"size:100"` → VARCHAR(100)

// not null: 非空约束
Name string `gorm:"not null"` → VARCHAR(255) NOT NULL

// default: 默认值
Status string `gorm:"default:'active'"` → VARCHAR(255) DEFAULT 'active'

// unique: 唯一约束
Email string `gorm:"unique"` → VARCHAR(255) UNIQUE

// index: 创建索引
Name string `gorm:"index"` → 创建索引 idx_<table>_name

// uniqueIndex: 唯一索引
Email string `gorm:"uniqueIndex"` → 创建唯一索引

// autoCreateTime: 自动创建时间
CreatedAt time.Time `gorm:"autoCreateTime"` → TIMESTAMP DEFAULT CURRENT_TIMESTAMP

// autoUpdateTime: 自动更新时间
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
// 方式 1: 使用 GORM 标签
type CustomType string

type User struct {
Data CustomType `gorm:"type:varchar(500)"`
}

// 方式 2: 实现 GormDataTypeInterface
func (CustomType) GormDataType() string {
return "varchar(500)"
}

// 方式 3: 自定义 Dialector
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"`
}

// 生成的 SQL
// MySQL: id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY
// PostgreSQL: id BIGSERIAL PRIMARY KEY
// SQLite: id INTEGER PRIMARY KEY AUTOINCREMENT

// 复合主键
type OrderItem struct {
OrderID uint `gorm:"primaryKey"`
ProductID uint `gorm:"primaryKey"`
}

// 生成的 SQL
// PRIMARY KEY (order_id, product_id)

外键约束:

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"`
}

// 生成的 SQL
// ALTER TABLE orders
// ADD CONSTRAINT fk_orders_user
// FOREIGN KEY (user_id) REFERENCES users(id)
// ON DELETE SET NULL
// ON UPDATE CASCADE

唯一约束:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type User struct {
Email string `gorm:"unique"`
}

// 生成的 SQL
// UNIQUE INDEX idx_users_email ON users(email)

// 复合唯一约束
type User struct {
FirstName string
LastName string
}

// 创建复合唯一索引
db.Migrator().CreateIndex(&User{}, "idx_users_name")
// CREATE UNIQUE INDEX idx_users_name ON users(first_name, last_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")
// CREATE INDEX idx_user_product_status ON orders(user_id, product_id, 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 {
// === 1. 检查表是否存在 ===
if !db.Migrator().HasTable(&User{}) {
if err := db.Migrator().CreateTable(&User{}); err != nil {
return err
}
}

// === 2. 检查列是否存在 ===
if !db.Migrator().HasColumn(&User{}, "avatar") {
if err := db.Migrator().AddColumn(&User{}, "avatar"); err != nil {
return err
}
}

// === 3. 检查并修改列类型 ===
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
}
}
}

// === 4. 检查索引是否存在 ===
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 // 版本号: 20240101_120000
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 {
// === 1. 创建迁移历史表 ===
db.Migrator().AutoMigrate(&MigrationHistory{})

// === 2. 获取已执行的迁移 ===
var executed []string
db.Model(&MigrationHistory{}).Pluck("version", &executed)

// === 3. 执行未执行的迁移 ===
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
// MySQL
func (m MySQL) CreateTable(dst ...interface{}) error {
return m.RunWithValue(dst, "", func(stmt *Statement) error {
for _, column := range stmt.Schema.Fields {
// MySQL 特定语法
if column.PrimaryKey {
sql += " AUTO_INCREMENT"
}
}
return nil
})
}

// PostgreSQL
func (m Postgres) CreateTable(dst ...interface{}) error {
return m.RunWithValue(dst, "", func(stmt *Statement) error {
for _, column := range stmt.Schema.Fields {
// PostgreSQL 特定语法
if column.PrimaryKey {
sql += " SERIAL"
}
}
return nil
})
}

// SQLite
func (m SQLite) CreateTable(dst ...interface{}) error {
return m.RunWithValue(dst, "", func(stmt *Statement) error {
for _, column := range stmt.Schema.Fields {
// SQLite 特定语法
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
// 方式 1: AutoMigrate (推荐开发环境)
db.AutoMigrate(&User{}, &Order{}, &Product{})

// 优点:
// - 简单快速
// - 自动处理
// - 适合开发环境

// 缺点:
// - 无法回滚
// - 无法版本控制
// - 可能导致数据丢失

// 方式 2: 版本化迁移 (推荐生产环境)
RunMigrations(db)

// 优点:
// - 可回滚
// - 版本控制
// - 安全可控
// - 适合生产环境

// 缺点:
// - 需要手动维护
// - 开发速度慢

方法 2: 实验法

实验 1: 观察迁移 SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 启用 SQL 日志
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})

// 执行迁移
db.AutoMigrate(&User{})

// 观察日志:
// [2024-01-01 10:00:00] CREATE TABLE `users` (
// `id` bigint unsigned AUTO_INCREMENT PRIMARY KEY,
// `name` varchar(100) NOT NULL,
// `email` varchar(255),
// `created_at` timestamp NULL,
// `updated_at` timestamp NULL
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

实验 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{})
// 创建表: CREATE TABLE users (id INT, name VARCHAR(255))

// 修改模型
type User struct {
ID uint
Name string
Avatar string // 新增字段
}

db.AutoMigrate(&User{})
// 增量迁移: ALTER TABLE users ADD COLUMN avatar VARCHAR(255)

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: 场景驱动

场景序列:

  1. 开发环境快速迭代

    • 使用 AutoMigrate
    • 允许删除重建
    • 优先开发速度
  2. 生产环境零停机

    • 使用版本化迁移
    • 增量更新
    • 支持回滚
  3. 大数据表迁移

    • 分批迁移
    • 在线变更
    • 降级策略

三、学习路径建议

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 学习成果验收

理论验收:

  • 能解释 AutoMigrate 的原理
  • 能说明类型映射机制
  • 能理解增量迁移策略
  • 能对比不同迁移方式

实践验收:

  • 能使用 AutoMigrate
  • 能使用 Migrator API
  • 能管理约束和索引
  • 能实现版本化迁移

综合验收:

  • 能设计迁移策略
  • 能优化迁移性能
  • 能排查迁移问题
  • 能向他人讲解迁移系统