本文档介绍项目的数据库迁移机制和最佳实践。
项目使用 GORM 作为 ORM,采用两层迁移策略:
- AutoMigrate:自动处理新增表和列
- 自定义迁移:处理特殊情况(重命名、数据迁移等)
应用启动
↓
db.Initialize()
↓
AutoMigrate(所有模型) ← 自动创建/添加新表和列
↓
RunMigrations() ← 执行自定义迁移
↓
应用就绪
GORM 的 AutoMigrate 会自动:
- ✅ 创建新表
- ✅ 添加新列
- ✅ 创建索引
AutoMigrate 不会:
- ❌ 删除列
- ❌ 重命名列
- ❌ 修改列类型
- ❌ 数据迁移
这些操作需要通过自定义迁移处理。
internal/repository/sqlite/migrations.go
type Migration struct {
Version int
Description string
Up func(db *gorm.DB) error
Down func(db *gorm.DB) error
}var migrations = []Migration{
{
Version: 1,
Description: "rename old_column to new_column in users table",
Up: func(db *gorm.DB) error {
if db.Migrator().HasColumn(&User{}, "old_column") {
return db.Migrator().RenameColumn(&User{}, "old_column", "new_column")
}
return nil
},
Down: func(db *gorm.DB) error {
if db.Migrator().HasColumn(&User{}, "new_column") {
return db.Migrator().RenameColumn(&User{}, "new_column", "old_column")
}
return nil
},
},
}{
Version: 2,
Description: "remove deprecated_field from orders",
Up: func(db *gorm.DB) error {
if db.Migrator().HasColumn(&Order{}, "deprecated_field") {
return db.Migrator().DropColumn(&Order{}, "deprecated_field")
}
return nil
},
Down: func(db *gorm.DB) error {
// 如果需要回滚,添加回列
return db.Migrator().AddColumn(&Order{}, "deprecated_field")
},
}{
Version: 3,
Description: "migrate data from old format to new format",
Up: func(db *gorm.DB) error {
// 使用事务保证数据一致性
return db.Exec(`
UPDATE users
SET full_name = first_name || ' ' || last_name
WHERE full_name IS NULL OR full_name = ''
`).Error
},
Down: func(db *gorm.DB) error {
// 数据迁移通常不可逆
return nil
},
}常用方法:
| 方法 | 说明 |
|---|---|
HasTable(&Model{}) |
检查表是否存在 |
HasColumn(&Model{}, "column") |
检查列是否存在 |
AddColumn(&Model{}, "column") |
添加列 |
DropColumn(&Model{}, "column") |
删除列 |
RenameColumn(&Model{}, "old", "new") |
重命名列 |
AlterColumn(&Model{}, "column") |
修改列类型 |
HasIndex(&Model{}, "index_name") |
检查索引是否存在 |
CreateIndex(&Model{}, "index_name") |
创建索引 |
DropIndex(&Model{}, "index_name") |
删除索引 |
RenameIndex(&Model{}, "old", "new") |
重命名索引 |
每个迁移必须有唯一的递增版本号:
var migrations = []Migration{
{Version: 1, ...},
{Version: 2, ...},
{Version: 3, ...},
}迁移应该是幂等的,重复执行不会出错:
Up: func(db *gorm.DB) error {
// ✅ 先检查再操作
if db.Migrator().HasColumn(&User{}, "old_column") {
return db.Migrator().RenameColumn(&User{}, "old_column", "new_column")
}
return nil
},描述应该清楚说明迁移的目的:
// ✅ 好的描述
Description: "rename user.email_address to user.email"
// ❌ 不好的描述
Description: "update users"尽量提供回滚方法,方便开发调试:
Down: func(db *gorm.DB) error {
// 回滚逻辑
return db.Migrator().RenameColumn(&User{}, "email", "email_address")
},复杂迁移应该使用事务:
Up: func(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
if err := tx.Exec("UPDATE ...").Error; err != nil {
return err
}
if err := tx.Exec("DELETE ...").Error; err != nil {
return err
}
return nil
})
},GORM 默认将 CamelCase 转换为 snake_case,但对于包含数字的字段需要特别注意:
// ❌ 默认会映射到 cache5m_write_count(没有下划线)
Cache5mWriteCount uint64
// ✅ 使用 column tag 指定正确的列名
Cache5mWriteCount uint64 `gorm:"column:cache_5m_write_count"`项目使用 Unix 毫秒时间戳存储时间:
type BaseModel struct {
ID uint64 `gorm:"primaryKey;autoIncrement"`
CreatedAt int64 `gorm:"not null"` // Unix 毫秒时间戳
UpdatedAt int64 `gorm:"not null"` // Unix 毫秒时间戳
}迁移记录存储在 schema_migrations 表:
SELECT * FROM schema_migrations;| version | description | applied_at |
|---|---|---|
| 1 | rename old_column to new_column | 1705500000000 |
| 2 | remove deprecated_field | 1705600000000 |
目前支持通过代码回滚到指定版本:
// 回滚到版本 1
db.RollbackMigration(1)A: 不需要。只需要在 GORM 模型中添加字段,AutoMigrate 会自动创建新列。
A: 以下情况需要自定义迁移:
- 重命名列
- 删除列
- 修改列类型
- 数据迁移/转换
- 复杂的索引操作
A: 迁移在事务中执行,失败会自动回滚。修复迁移代码后重新启动即可。
A:
- 备份数据库
- 在测试环境运行迁移
- 验证数据完整性
- 测试回滚功能