2. AutoMigrate 基础:方法签名、自动建表、自动增加字段、自动修改字段类型
AutoMigrate 是 GORM 提供的最便捷的数据库模式同步工具。它允许开发者通过定义 Go 结构体(Model),自动将结构体与数据库表结构保持同步,而无需手动编写 DDL 语句。本章将深入解析 AutoMigrate 的方法签名、核心行为以及在实际项目中的边界场景。
2.1 AutoMigrate 方法签名与核心参数
AutoMigrate 是 *gorm.DB 实例上的一个方法,其标准签名如下:
func (db *gorm.DB) AutoMigrate(dst ...interface{}) error
该方法接受一个或多个结构体指针作为参数,返回一个 error。执行时,GORM 会遍历所有传入的结构体,并与数据库中已存在的表进行对比,自动执行以下操作:
- 自动建表:如果表不存在,则根据结构体定义创建新表。
- 自动增加字段:如果表已存在,但结构体中新增了字段,则自动添加新列。
- 自动修改字段类型:如果表已存在,且结构体中字段类型发生变化,则尝试修改列类型。
- 自动增加索引:如果结构体定义了索引标签,且索引不存在,则自动创建。
2.2 自动建表:从结构体到数据库表
当数据库中没有对应的表时,AutoMigrate 会根据结构体定义创建一张新表。以下是一个典型的用户模型示例:
type User struct {
gorm.Model
Name string `gorm:"type:varchar(100);not null"`
Email string `gorm:"uniqueIndex;size:255"`
Age uint8 `gorm:"default:18"`
Birthday *time.Time
Active bool `gorm:"default:true"`
}
// 执行迁移
db.AutoMigrate(&User{})
执行上述代码后,GORM 会在数据库中生成一张名为 users 的表,其列定义与结构体字段一一对应。下表展示了 Go 类型与常见数据库类型的默认映射关系:
| Go 类型 | 默认数据库类型 (MySQL) | 说明 |
|---|---|---|
string |
longtext |
可通过 size 标签指定长度,如 size:255 变为 varchar(255) |
int, uint |
bigint |
根据位数自动选择 int/tinyint/smallint/mediumint/bigint |
bool |
tinyint(1) |
MySQL 无原生布尔类型,GORM 自动映射 |
float64 |
double |
可通过 precision 标签控制精度 |
time.Time |
datetime(3) |
支持毫秒精度 |
[]byte |
longblob |
适用于二进制数据 |
2.3 自动增加字段:结构体演进与列追加
当表已存在,但结构体中新增了字段时,AutoMigrate 会自动执行 ALTER TABLE ADD COLUMN。这是最常用的场景,也是 GORM 迁移的“安全区”。
假设 User 表已存在,现在需要增加一个 Phone 字段:
type User struct {
gorm.Model
Name string `gorm:"type:varchar(100);not null"`
Email string `gorm:"uniqueIndex;size:255"`
Age uint8 `gorm:"default:18"`
Birthday *time.Time
Active bool `gorm:"default:true"`
Phone string `gorm:"size:20"` // 新增字段
}
db.AutoMigrate(&User{})
执行后,数据库会自动执行类似 ALTER TABLE users ADD COLUMN phone varchar(20); 的语句。已存在的行数据不会受到影响,新列会使用结构体定义的默认值或数据库的零值填充。
not null 但没有默认值,且表中已有数据行,则 ALTER TABLE 可能会失败。建议新增非空字段时始终提供 default 标签,或先允许空值再手动填充。
2.4 自动修改字段类型:类型变更与兼容性风险
AutoMigrate 支持修改字段类型,但这是迁移中最容易引发问题的操作。当结构体字段类型或标签(如 size、type)发生变化时,GORM 会尝试执行 ALTER TABLE MODIFY COLUMN。
例如,将 Age 字段从 uint8 改为 uint16:
type User struct {
gorm.Model
Name string `gorm:"type:varchar(100);not null"`
Email string `gorm:"uniqueIndex;size:255"`
Age uint16 `gorm:"default:18"` // 类型从 uint8 改为 uint16
Birthday *time.Time
Active bool `gorm:"default:true"`
}
db.AutoMigrate(&User{})
GORM 会生成 ALTER TABLE users MODIFY COLUMN age bigint unsigned DEFAULT 18;。但以下场景会导致修改失败或数据截断:
- 缩小字段长度:如
varchar(255)改为varchar(50),若已有数据超过 50 字符,则执行失败。 - 不兼容的类型转换:如
string改为int,数据库无法自动转换。 - 精度丢失:如
float64改为float32,可能导致数据四舍五入。
2.5 AutoMigrate 执行流程与边界决策
以下 SVG 图展示了 AutoMigrate 在遇到不同表状态时的完整决策路径:
2.6 字段类型修改的边界场景与处理策略
当 AutoMigrate 检测到字段类型或标签发生变化时,会尝试执行 ALTER TABLE MODIFY COLUMN。下表总结了常见变更场景及其边界行为:
| 变更场景 | 示例 | AutoMigrate 行为 | 边界风险 |
|---|---|---|---|
| 扩大字段长度 | varchar(50) → varchar(100) |
成功执行 MODIFY | 低风险,兼容性好 |
| 缩小字段长度 | varchar(100) → varchar(50) |
可能失败 | 若存在超长数据则报错 |
| 更改字段类型族 | string → int |
可能失败 | 数据库无法隐式转换 |
| 更改数值精度 | float64 → float32 |
执行 MODIFY | 高精度数据被截断 |
| 添加/移除 NOT NULL | 添加 not null 标签 |
执行 MODIFY | 若存在 NULL 值则失败 |
| 更改默认值 | default:0 → default:1 |
执行 MODIFY | 仅影响新插入的行 |
2.7 多表迁移与执行顺序
AutoMigrate 支持同时传入多个模型,GORM 会按照传入顺序依次处理。如果模型之间存在外键依赖,需要确保父表先于子表迁移:
// 正确的顺序:Company 先于 User
db.AutoMigrate(&Company{}, &User{}, &Order{})
// 错误的顺序:User 依赖 Company,但 Company 尚未创建
// db.AutoMigrate(&User{}, &Company{}) // 可能导致外键创建失败
GORM 在迁移时会自动处理表之间的外键关系,但前提是依赖的表已经存在。如果遇到循环依赖(如双向外键),建议先迁移不带外键的版本,再通过后续迁移添加约束。
2.8 迁移日志与调试
开启 GORM 的日志功能可以实时查看 AutoMigrate 执行的 SQL 语句,这对于调试迁移问题至关重要:
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 开启 Info 级别日志
})
// 执行迁移时会输出类似以下日志:
// [info] migrating table: users
// [info] ALTER TABLE users ADD COLUMN phone varchar(20)
// [info] ALTER TABLE users MODIFY COLUMN age bigint unsigned DEFAULT 18
通过日志,开发者可以精确掌握 AutoMigrate 的每一步操作,及时发现并纠正潜在的边界问题。