automigrate只做安全新增:建表、加字段、加索引,不删字段、不改类型、不降not null;需传指针、配标签、显式启用外键并手动处理中间表与依赖顺序。

AutoMigrate 会创建表但不会修改已有字段
直接调用 db.AutoMigrate(&User{}) 是最常见做法,但它只做「新增」:新增表、新增字段、新增索引,不会删除字段,也不会修改字段类型或约束(比如把 string 改成 int,或给非空字段加 NOT NULL)。你改了 struct 字段类型后跑一遍,GORM 不报错,但数据库里字段还是老样子。
常见错误现象:AutoMigrate 执行完,SELECT * FROM users 查不到新字段,或者插入时报 column "xxx" does not exist —— 其实是字段根本没加进去。
- 只对尚未存在的表执行建表;已存在表仅尝试添加缺失字段/索引
- 字段名变更(如
Name→Fullname)会被当成两个字段,旧字段残留,新字段新增 - 若想改类型或删字段,必须手动写 SQL 或用
db.Migrator().DropColumn()+AddColumn()
嵌套结构体和关联字段不会自动建外键
GORM 默认不为 gorm.Model 或自定义 UserID uint 这类字段自动加外键约束,哪怕你写了 gorm.ForeignKey tag。它只生成普通字段,除非显式启用外键支持并配好关联标签。
使用场景:你定义了 type Order struct { UserID uint } ,期望生成 user_id BIGINT REFERENCES users(id),但实际只得到 user_id bigint。
- 必须在初始化时开启外键:
gorm.Open(postgres.Open(dsn), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: false}) - 关联字段需配完整 tag:
UserID uint `gorm:"foreignKey:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` -
gorm.JoinTable的中间表不会被AutoMigrate自动创建,得单独迁一次对应 struct
多个模型迁移要按依赖顺序调用
如果 User 和 Profile 之间有外键引用,而你先 AutoMigrate(&Profile{}) 再 AutoMigrate(&User{}),GORM 可能因目标表不存在跳过外键创建(尤其 DisableForeignKeyConstraintWhenMigrating: true 时默认行为)。
性能影响:并发调用多个 AutoMigrate 不安全,GORM 内部不是原子操作,可能触发重复建表或锁表失败。
- 按外键依赖顺序排列:先迁被引用方(如
User),再迁引用方(如Order) - 合并到一次调用更稳妥:
db.AutoMigrate(&User{}, &Order{}, &Product{}) - 生产环境避免在服务启动时无条件执行 —— 建议加开关或检查
db.Migrator().HasTable(&User{})再决定是否迁
SQLite 和 MySQL 对 AutoMigrate 的兼容性差异
SQLite 几乎不支持 ALTER COLUMN,所以 AutoMigrate 在 SQLite 中无法修改字段类型或 NOT NULL 约束;MySQL 8.0+ 支持部分修改(如加 NOT NULL),但 GORM 仍选择绕过,保持行为一致 —— 即所有数据库都只做「安全新增」。
容易踩的坑:本地用 SQLite 开发,字段改了能跑通;上线 MySQL 后发现同样代码没生效,其实是 SQLite 偷懒没报错,掩盖了迁移逻辑缺陷。
- SQLite 下
AutoMigrate遇到字段类型变更会静默忽略(不报错也不改) - MySQL 5.7 默认严格模式下,若新字段设了
NOT NULL但表里已有数据,AutoMigrate会直接 panic 报Error 1138: Invalid use of NULL value - 跨库迁移一致性要求高时,建议统一用
db.Migrator().CreateTable()+ 手动AlterColumn控制节奏
真正难的不是让表建出来,而是让字段变更可预期、可回滚、不破坏存量数据。AutoMigrate 适合初始搭建或字段只增不改的场景,一旦业务跑起来,就得切到版本化迁移工具(比如 gormigrate 或原生 SQL 脚本)。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











