迁移与结构管理


第九章:迁移与结构管理

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 练习题

  1. 编写一个迁移脚本,安全地将 User 表的 Name 列长度从 50 改为 100
  2. 实现一个检查迁移状态的工具,列出已执行和待执行的迁移
  3. 为现有表添加软删除支持(添加 deleted_at 列)

9.13 小结

本章讲解了 GORM 的迁移功能,包括自动迁移、手动迁移和版本化迁移策略。生产环境建议使用版本化迁移工具,确保数据库变更有序可控。


本文代码地址:https://github.com/LittleMoreInteresting/gorm_study

欢迎关注公众号,一起学习进步!

如有疑问关注公众号给我留言
wx

关注公众号

©2017-2023 鲁ICP备17023316号-1 Powered by Hugo