修改表结构

表创建后,需求变化需要修改结构。GORM 提供了修改表结构的方法。

添加字段

type User struct {
    ID       uint
    Name     string
    Age      int
    Nickname string `gorm:"type:varchar(50)"`
}

db.Migrator().AddColumn(&User{}, "Nickname")

或者:

db.Migrator().AutoMigrate(&User{})

AutoMigrate 会自动添加缺少的字段。

删除字段

db.Migrator().DropColumn(&User{}, "nickname")

注意:删除字段会丢失数据,谨慎操作。

修改字段

修改字段类型:

db.Migrator().AlterColumn(&User{}, "name")

这会根据模型定义修改字段类型。但不是所有数据库都支持修改字段类型。

SQLite 不支持 ALTER COLUMN,需要重建表。

重命名字段

db.Migrator().RenameColumn(&User{}, "name", "username")

数据会保留,只是字段名改变。

检查字段存在

if db.Migrator().HasColumn(&User{}, "nickname") {
    db.Migrator().DropColumn(&User{}, "nickname")
}

重命名表

db.Migrator().RenameTable(&User{}, &UserInfo{})

或者:

db.Migrator().RenameTable("users", "user_infos")

迁移器方法总结

方法说明
AddColumn添加字段
DropColumn删除字段
AlterColumn修改字段
RenameColumn重命名字段
HasColumn检查字段存在
CreateTable创建表
DropTable删除表
HasTable检查表存在
RenameTable重命名表
CreateIndex创建索引
DropIndex删除索引
HasIndex检查索引存在

原生 SQL 迁移

复杂修改用原生 SQL:

db.Exec(`
    ALTER TABLE users
    ADD COLUMN nickname VARCHAR(50) AFTER name,
    MODIFY COLUMN age INT DEFAULT 18
`)

PostgreSQL:

db.Exec(`
    ALTER TABLE users
    ADD COLUMN nickname VARCHAR(50),
    ALTER COLUMN age SET DEFAULT 18
`)

迁移脚本示例

版本化迁移:

func MigrateV1(db *gorm.DB) error {
    return db.Transaction(func(tx *gorm.DB) error {
        if err := tx.Migrator().AddColumn(&User{}, "nickname"); err != nil {
            return err
        }
        if err := tx.Migrator().AddColumn(&User{}, "avatar"); err != nil {
            return err
        }
        return nil
    })
}

func MigrateV2(db *gorm.DB) error {
    return db.Transaction(func(tx *gorm.DB) error {
        if err := tx.Migrator().RenameColumn(&User{}, "nickname", "display_name"); err != nil {
            return err
        }
        return nil
    })
}

数据迁移

修改结构同时迁移数据:

func MigrateUserStatus(db *gorm.DB) error {
    return db.Transaction(func(tx *gorm.DB) error {
        if err := tx.Exec(`
            ALTER TABLE users ADD COLUMN status VARCHAR(20) DEFAULT 'active'
        `).Error; err != nil {
            return err
        }

        if err := tx.Exec(`
            UPDATE users SET status = CASE
                WHEN is_active = 1 THEN 'active'
                ELSE 'inactive'
            END
        `).Error; err != nil {
            return err
        }

        return tx.Exec(`ALTER TABLE users DROP COLUMN is_active`).Error
    })
}

数据库差异

MySQL

ALTER TABLE users ADD COLUMN nickname VARCHAR(50) AFTER name;
ALTER TABLE users MODIFY COLUMN age INT DEFAULT 18;

PostgreSQL

ALTER TABLE users ADD COLUMN nickname VARCHAR(50);
ALTER TABLE users ALTER COLUMN age SET DEFAULT 18;

SQLite

SQLite 的 ALTER TABLE 功能有限,复杂修改需要:

  1. 创建新表
  2. 复制数据
  3. 删除旧表
  4. 重命名新表
func MigrateSQLite(db *gorm.DB) error {
    return db.Transaction(func(tx *gorm.DB) error {
        tx.Exec(`
            CREATE TABLE users_new (
                id INTEGER PRIMARY KEY,
                name TEXT,
                nickname TEXT,
                age INTEGER DEFAULT 18
            )
        `)

        tx.Exec(`
            INSERT INTO users_new (id, name, age)
            SELECT id, name, age FROM users
        `)

        tx.Exec(`DROP TABLE users`)
        tx.Exec(`ALTER TABLE users_new RENAME TO users`)

        return nil
    })
}

实际案例

添加用户角色

func AddUserRole(db *gorm.DB) error {
    if db.Migrator().HasColumn(&User{}, "role") {
        return nil
    }

    return db.Transaction(func(tx *gorm.DB) error {
        if err := tx.Exec(`
            ALTER TABLE users ADD COLUMN role VARCHAR(20) DEFAULT 'user'
        `).Error; err != nil {
            return err
        }

        return tx.Exec(`
            UPDATE users SET role = 'admin' WHERE is_admin = 1
        `).Error
    })
}

拆分地址字段

func SplitAddress(db *gorm.DB) error {
    return db.Transaction(func(tx *gorm.DB) error {
        tx.Exec(`ALTER TABLE users ADD COLUMN province VARCHAR(50)`)
        tx.Exec(`ALTER TABLE users ADD COLUMN city VARCHAR(50)`)
        tx.Exec(`ALTER TABLE users ADD COLUMN district VARCHAR(50)`)
        tx.Exec(`ALTER TABLE users ADD COLUMN detail TEXT`)

        tx.Exec(`
            UPDATE users SET
                province = SUBSTRING_INDEX(address, ' ', 1),
                city = SUBSTRING_INDEX(SUBSTRING_INDEX(address, ' ', 2), ' ', -1),
                detail = SUBSTRING_INDEX(address, ' ', -1)
        `)

        return tx.Exec(`ALTER TABLE users DROP COLUMN address`).Error
    })
}

小结

修改表结构要谨慎,生产环境务必备份。理解不同数据库的差异,复杂迁移用原生 SQL。