2. AutoMigrate 基础:方法签名、自动建表、自动增加字段、自动修改字段类型

AutoMigrate 是 GORM 提供的最便捷的数据库模式同步工具。它允许开发者通过定义 Go 结构体(Model),自动将结构体与数据库表结构保持同步,而无需手动编写 DDL 语句。本章将深入解析 AutoMigrate 的方法签名、核心行为以及在实际项目中的边界场景。

2.1 AutoMigrate 方法签名与核心参数

AutoMigrate 是 *gorm.DB 实例上的一个方法,其标准签名如下:

func (db *gorm.DB) AutoMigrate(dst ...interface{}) error

该方法接受一个或多个结构体指针作为参数,返回一个 error。执行时,GORM 会遍历所有传入的结构体,并与数据库中已存在的表进行对比,自动执行以下操作:

  • 自动建表:如果表不存在,则根据结构体定义创建新表。
  • 自动增加字段:如果表已存在,但结构体中新增了字段,则自动添加新列。
  • 自动修改字段类型:如果表已存在,且结构体中字段类型发生变化,则尝试修改列类型。
  • 自动增加索引:如果结构体定义了索引标签,且索引不存在,则自动创建。
关键提示: AutoMigrate 不会删除已存在的列或索引,也不会重命名列。这是一种“只增不改(除类型外)”的安全策略,旨在避免生产环境下的数据丢失。

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 在遇到不同表状态时的完整决策路径:

AutoMigrate 执行流程与边界决策 AutoMigrate 调用 表是否存在? 否 CREATE TABLE 是 对比结构差异 有新增字段? 是 ALTER ADD COLUMN 否 检查字段类型变更 迁移完成

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 仅影响新插入的行
最佳实践: 在生产环境中,建议将 AutoMigrate 仅用于开发/测试阶段的快速原型验证。对于生产数据库的字段类型变更,应使用手动编写的、经过充分测试的迁移脚本,并在执行前做好数据备份。

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 的每一步操作,及时发现并纠正潜在的边界问题。