数据库迁移 #

一、迁移概述 #

1.1 什么是数据库迁移 #

数据库迁移是管理数据库结构变化的过程:

text
版本1 → 创建用户表
版本2 → 添加邮箱字段
版本3 → 添加索引
版本4 → 创建订单表

1.2 迁移方式 #

方式 说明 适用场景
AutoMigrate 自动迁移 开发环境
Migrator 编程迁移 复杂场景
迁移工具 版本控制 生产环境

二、AutoMigrate #

2.1 基本用法 #

go
type User struct {
    ID        uint   `gorm:"primaryKey"`
    Name      string `gorm:"size:100"`
    Email     string `gorm:"size:100;uniqueIndex"`
    Age       int
    CreatedAt time.Time
    UpdatedAt time.Time
}

func main() {
    // 自动迁移
    if err := DB.AutoMigrate(&User{}); err != nil {
        panic(err)
    }
}

2.2 多表迁移 #

go
func main() {
    // 迁移多个表
    if err := DB.AutoMigrate(
        &User{},
        &Post{},
        &Comment{},
    ); err != nil {
        panic(err)
    }
}

2.3 AutoMigrate特点 #

text
特点:
✓ 创建不存在的表
✓ 创建不存在的列
✓ 创建不存在的索引
✗ 不会删除列
✗ 不会删除索引
✗ 不会改变列类型

三、Migrator #

3.1 创建表 #

go
func main() {
    migrator := DB.Migrator()
    
    // 创建表
    if err := migrator.CreateTable(&User{}); err != nil {
        panic(err)
    }
    
    // 检查表是否存在
    if migrator.HasTable(&User{}) {
        fmt.Println("表已存在")
    }
}

3.2 删除表 #

go
func main() {
    migrator := DB.Migrator()
    
    // 删除表
    if err := migrator.DropTable(&User{}); err != nil {
        panic(err)
    }
}

3.3 重命名表 #

go
func main() {
    migrator := DB.Migrator()
    
    // 重命名表
    if err := migrator.RenameTable(&User{}, &UserInfo{}); err != nil {
        panic(err)
    }
}

3.4 列操作 #

go
func main() {
    migrator := DB.Migrator()
    
    // 添加列
    if err := migrator.AddColumn(&User{}, "Phone"); err != nil {
        panic(err)
    }
    
    // 删除列
    if err := migrator.DropColumn(&User{}, "Phone"); err != nil {
        panic(err)
    }
    
    // 重命名列
    if err := migrator.RenameColumn(&User{}, "Name", "Username"); err != nil {
        panic(err)
    }
    
    // 检查列是否存在
    if migrator.HasColumn(&User{}, "Phone") {
        fmt.Println("列已存在")
    }
}

3.5 索引操作 #

go
func main() {
    migrator := DB.Migrator()
    
    // 创建索引
    if err := migrator.CreateIndex(&User{}, "idx_name"); err != nil {
        panic(err)
    }
    
    // 删除索引
    if err := migrator.DropIndex(&User{}, "idx_name"); err != nil {
        panic(err)
    }
    
    // 检查索引是否存在
    if migrator.HasIndex(&User{}, "idx_name") {
        fmt.Println("索引已存在")
    }
}

四、索引管理 #

4.1 模型定义索引 #

go
type User struct {
    // 主键索引
    ID uint `gorm:"primaryKey"`
    
    // 唯一索引
    Email string `gorm:"uniqueIndex"`
    
    // 普通索引
    Name string `gorm:"index"`
    
    // 复合索引
    FirstName string `gorm:"index:idx_name"`
    LastName  string `gorm:"index:idx_name"`
    
    // 条件索引
    Status string `gorm:"index:idx_status,where:status='active'"`
    
    // 前缀索引
    Address string `gorm:"index:idx_address,length:10"`
}

4.2 创建索引 #

go
func main() {
    // 自动迁移会创建索引
    DB.AutoMigrate(&User{})
    
    // 手动创建索引
    DB.Exec("CREATE INDEX idx_name ON users(name)")
    DB.Exec("CREATE UNIQUE INDEX idx_email ON users(email)")
    DB.Exec("CREATE INDEX idx_name_age ON users(name, age)")
}

4.3 删除索引 #

go
func main() {
    migrator := DB.Migrator()
    
    // 删除索引
    migrator.DropIndex(&User{}, "idx_name")
    migrator.DropIndex(&User{}, "idx_email")
}

五、迁移工具 #

5.1 使用goose #

bash
go install github.com/pressly/goose/v3/cmd/goose@latest

创建迁移文件:

bash
goose create create_users_table sql

迁移文件示例:

sql
-- +goose Up
CREATE TABLE users (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE,
    age INT DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP NULL
);

CREATE INDEX idx_users_name ON users(name);
CREATE INDEX idx_users_deleted_at ON users(deleted_at);

-- +goose Down
DROP TABLE IF EXISTS users;

执行迁移:

bash
# 升级
goose mysql "user:password@tcp(localhost:3306)/dbname" up

# 回滚
goose mysql "user:password@tcp(localhost:3306)/dbname" down

# 查看状态
goose mysql "user:password@tcp(localhost:3306)/dbname" status

5.2 使用golang-migrate #

bash
go install -tags 'mysql' github.com/golang-migrate/migrate/v4/cmd/migrate@latest

创建迁移文件:

bash
migrate create -ext sql -dir migrations -seq create_users_table

迁移文件示例:

sql
-- migrations/000001_create_users_table.up.sql
CREATE TABLE users (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- migrations/000001_create_users_table.down.sql
DROP TABLE IF EXISTS users;

执行迁移:

bash
# 升级
migrate -database "mysql://user:password@tcp(localhost:3306)/dbname" -path migrations up

# 回滚
migrate -database "mysql://user:password@tcp(localhost:3306)/dbname" -path migrations down

# 查看版本
migrate -database "mysql://user:password@tcp(localhost:3306)/dbname" -path migrations version

六、迁移最佳实践 #

6.1 迁移文件组织 #

text
migrations/
├── 000001_create_users_table.up.sql
├── 000001_create_users_table.down.sql
├── 000002_add_user_phone.up.sql
├── 000002_add_user_phone.down.sql
├── 000003_create_posts_table.up.sql
└── 000003_create_posts_table.down.sql

6.2 迁移规范 #

sql
-- 向上迁移:创建表
-- +goose Up
CREATE TABLE users (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 向下迁移:删除表
-- +goose Down
DROP TABLE IF EXISTS users;

6.3 安全迁移 #

sql
-- 安全添加列(带默认值)
ALTER TABLE users ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'active';

-- 安全添加索引(不锁表)
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);

-- 安全删除列
-- 1. 先修改代码不再使用该列
-- 2. 部署新代码
-- 3. 删除列
ALTER TABLE users DROP COLUMN old_column;

七、完整迁移示例 #

7.1 初始化迁移 #

go
package database

import (
    "gorm.io/gorm"
)

func Migrate(db *gorm.DB) error {
    // 自动迁移
    if err := db.AutoMigrate(
        &User{},
        &Post{},
        &Comment{},
        &Tag{},
    ); err != nil {
        return err
    }
    
    // 手动创建索引
    if err := createIndexes(db); err != nil {
        return err
    }
    
    return nil
}

func createIndexes(db *gorm.DB) error {
    // 创建全文索引
    if err := db.Exec("CREATE FULLTEXT INDEX idx_posts_content ON posts(content)").Error; err != nil {
        return err
    }
    
    return nil
}

7.2 迁移管理 #

go
package main

import (
    "flag"
    
    "github.com/gin-gonic/gin"
)

func main() {
    migrate := flag.Bool("migrate", false, "Run database migrations")
    flag.Parse()
    
    // 初始化数据库
    db := InitDB()
    
    // 执行迁移
    if *migrate {
        if err := database.Migrate(db); err != nil {
            panic(err)
        }
        fmt.Println("Migration completed")
        return
    }
    
    r := gin.Default()
    r.Run()
}

7.3 迁移命令 #

bash
# 运行迁移
go run main.go -migrate

# 启动服务
go run main.go

八、总结 #

8.1 核心要点 #

要点 说明
AutoMigrate 自动迁移表结构
Migrator 编程式迁移
迁移工具 版本控制迁移
索引管理 创建和管理索引

8.2 最佳实践 #

实践 说明
版本控制 使用迁移工具管理版本
可回滚 每个迁移都要有回滚脚本
测试验证 迁移前先在测试环境验证
备份数据 迁移前备份数据库

8.3 下一步 #

现在你已经掌握了数据库迁移,接下来让我们学习 JWT认证,了解Gin中的认证实现!

最后更新:2026-03-28