迁移与结构管理
第九章:迁移与结构管理
9.1 自动迁移概述
GORM 的自动迁移功能可以根据模型定义自动创建或更新数据库表结构。
// 基础用法
db.AutoMigrate(&User{})
// 多个模型
db.AutoMigrate(&User{}, &Product{}, &Order{})
// 指定表名
db.Table("my_users").AutoMigrate(&User{})
9.2 AutoMigrate 行为
自动迁移会做什么
- 创建新表
- 添加缺失的列
- 创建缺失的索引和外键
自动迁移不会做什么
- 不会删除不用的列(保护数据)
- 不会修改列类型(可能导致数据丢失)
- 不会删除索引
- 不会重命名列
9.3 迁移方法
普通迁移
db.AutoMigrate(&User{})
静默迁移(不输出日志)
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})
检查表是否存在
// 检查表是否存在
hasTable := db.Migrator().HasTable(&User{})
hasTable = db.Migrator().HasTable("users")
// 检查列是否存在
hasColumn := db.Migrator().HasColumn(&User{}, "Name")
// 检查索引是否存在
hasIndex := db.Migrator().HasIndex(&User{}, "idx_name")
9.4 表操作
创建表
db.Migrator().CreateTable(&User{})
// 带有选项
db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4").Migrator().CreateTable(&User{})
删除表
db.Migrator().DropTable(&User{})
db.Migrator().DropTable("users")
db.Migrator().DropTable("users", "products")
重命名表
db.Migrator().RenameTable(&User{}, &UserInfo{})
db.Migrator().RenameTable("users", "user_infos")
9.5 列操作
添加列
db.Migrator().AddColumn(&User{}, "Age")
删除列
db.Migrator().DropColumn(&User{}, "Age")
修改列类型
db.Migrator().AlterColumn(&User{}, "Age")
重命名列
db.Migrator().RenameColumn(&User{}, "Name", "NickName")
9.6 索引操作
创建索引
db.Migrator().CreateIndex(&User{}, "idx_name")
db.Migrator().CreateIndex(&User{}, "Name")
删除索引
db.Migrator().DropIndex(&User{}, "idx_name")
重命名索引
db.Migrator().RenameIndex(&User{}, "idx_name", "idx_user_name")
9.7 外键操作
创建外键
db.Migrator().CreateConstraint(&User{}, "Company")
db.Migrator().CreateConstraint(&User{}, "fk_users_company")
删除外键
db.Migrator().DropConstraint(&User{}, "Company")
9.8 约束标签
type User struct {
ID uint
Name string `gorm:"size:100;not null;uniqueIndex"`
Email string `gorm:"uniqueIndex:idx_email;check:email <> ''"`
Age int `gorm:"check:age > 0 AND age < 150"`
CompanyID int `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
约束类型
| 约束 | 说明 |
|---|---|
not null |
非空约束 |
unique |
唯一约束 |
uniqueIndex |
唯一索引 |
index |
普通索引 |
check |
检查约束 |
constraint |
外键约束 |
9.9 数据库特定选项
MySQL
type User struct {
ID uint
Name string `gorm:"type:varchar(100);charset:utf8mb4;collation:utf8mb4_unicode_ci"`
}
// 表选项
db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表'").AutoMigrate(&User{})
PostgreSQL
type User struct {
ID uint
Name string `gorm:"type:varchar(100)"`
}
// 表空间等选项
db.Set("gorm:table_options", "TABLESPACE pg_default").AutoMigrate(&User{})
9.10 版本化迁移
对于生产环境,推荐使用版本化迁移工具:
使用 golang-migrate
# 安装
go install -tags 'mysql' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
# 创建迁移文件
migrate create -ext sql -dir migrations -seq create_users_table
migrate create -ext sql -dir migrations -seq add_user_email_index
# 执行迁移
migrate -path migrations -database "mysql://user:password@tcp(localhost:3306)/mydb" up
使用 GORM 的 Migrator 编写版本化迁移
package migration
import (
"gorm.io/gorm"
)
type Migration struct {
ID string
Up func(*gorm.DB) error
Down func(*gorm.DB) error
}
var migrations = []Migration{
{
ID: "202401010001_create_users",
Up: func(db *gorm.DB) error {
return db.AutoMigrate(&User{})
},
Down: func(db *gorm.DB) error {
return db.Migrator().DropTable(&User{})
},
},
{
ID: "202401010002_add_user_age",
Up: func(db *gorm.DB) error {
return db.Migrator().AddColumn(&User{}, "Age")
},
Down: func(db *gorm.DB) error {
return db.Migrator().DropColumn(&User{}, "Age")
},
},
}
func RunMigrations(db *gorm.DB) error {
// 创建迁移记录表
if !db.Migrator().HasTable(&MigrationRecord{}) {
db.AutoMigrate(&MigrationRecord{})
}
for _, m := range migrations {
var record MigrationRecord
if err := db.Where("id = ?", m.ID).First(&record).Error; err == nil {
continue // 已执行过
}
if err := m.Up(db); err != nil {
return err
}
db.Create(&MigrationRecord{ID: m.ID})
}
return nil
}
type MigrationRecord struct {
ID string `gorm:"primaryKey"`
AppliedAt time.Time
}
9.11 实战:零停机迁移
// 1. 添加新列(可空)
db.Migrator().AddColumn(&User{}, "NewEmail")
// 2. 双写(应用代码同时写旧列和新列)
// 应用部署中...
// 3. 回填数据
db.Model(&User{}).UpdateColumn("NewEmail", gorm.Expr("Email"))
// 4. 新列设为 Not Null
db.Migrator().AlterColumn(&User{}, "NewEmail")
// 5. 删除旧列(后续版本)
db.Migrator().RenameColumn(&User{}, "NewEmail", "Email")
9.12 练习题
- 编写一个迁移脚本,安全地将
User表的Name列长度从 50 改为 100 - 实现一个检查迁移状态的工具,列出已执行和待执行的迁移
- 为现有表添加软删除支持(添加 deleted_at 列)
9.13 小结
本章讲解了 GORM 的迁移功能,包括自动迁移、手动迁移和版本化迁移策略。生产环境建议使用版本化迁移工具,确保数据库变更有序可控。
本文代码地址:https://github.com/LittleMoreInteresting/gorm_study
欢迎关注公众号,一起学习进步!