表创建后,需求变化需要修改结构。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:
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 功能有限,复杂修改需要:
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。