数据库迁移是敏感操作,处理不当可能导致数据丢失或服务中断。遵循最佳实践,确保迁移安全可靠。
每次迁移对应一个版本号,按顺序执行:
var migrations = []func(*gorm.DB) error{
V1CreateUsersTable,
V2AddUserNickname,
V3CreateOrdersTable,
V4AddOrderStatus,
}
func RunMigrations(db *gorm.DB) error {
for _, m := range migrations {
if err := m(db); err != nil {
return err
}
}
return nil
}
func V1CreateUsersTable(db *gorm.DB) error {
return db.Migrator().CreateTable(&User{})
}
func V2AddUserNickname(db *gorm.DB) error {
return db.Migrator().AddColumn(&User{}, "nickname")
}
记录已执行的迁移:
type Migration struct {
ID uint
Name string
Batch int
ExecutedAt time.Time
}
func RunMigration(db *gorm.DB, name string, fn func(*gorm.DB) error) error {
if db.Migrator().HasTable(&Migration{}) == false {
db.Migrator().CreateTable(&Migration{})
}
var count int64
db.Model(&Migration{}).Where("name = ?", name).Count(&count)
if count > 0 {
return nil
}
return db.Transaction(func(tx *gorm.DB) error {
if err := fn(tx); err != nil {
return err
}
return tx.Create(&Migration{Name: name}).Error
})
}
迁移前备份:
func BackupTable(db *gorm.DB, table string) error {
backupTable := table + "_backup_" + time.Now().Format("20060102150405")
return db.Exec(fmt.Sprintf("CREATE TABLE %s AS SELECT * FROM %s", backupTable, table)).Error
}
每个迁移提供 up 和 down:
type MigrationFunc struct {
Up func(*gorm.DB) error
Down func(*gorm.DB) error
}
var migrations = map[string]MigrationFunc{
"20240101000001_create_users": {
Up: func(db *gorm.DB) error {
return db.Migrator().CreateTable(&User{})
},
Down: func(db *gorm.DB) error {
return db.Migrator().DropTable(&User{})
},
},
"20240102000001_add_nickname": {
Up: func(db *gorm.DB) error {
return db.Migrator().AddColumn(&User{}, "nickname")
},
Down: func(db *gorm.DB) error {
return db.Migrator().DropColumn(&User{}, "nickname")
},
},
}
将迁移脚本放在独立目录:
migrations/
├── 20240101000001_create_users.sql
├── 20240102000001_add_nickname.sql
└── 20240103000001_create_orders.sql
使用迁移工具如 golang-migrate、goose 等。
开发环境用 AutoMigrate,生产环境用版本化迁移:
func RunMigrations(db *gorm.DB) error {
if os.Getenv("ENV") == "production" {
return runVersionedMigrations(db)
}
return db.AutoMigrate(&User{}, &Order{})
}
迁移前检查:
迁移后检查:
大表迁移避免锁表:
添加字段(MySQL)
ALTER TABLE users ADD COLUMN nickname VARCHAR(50), ALGORITHM=INPLACE, LOCK=NONE;
创建索引
CREATE INDEX idx_name ON users(name), ALGORITHM=INPLACE, LOCK=NONE;
分批更新数据
func BatchUpdate(db *gorm.DB) error {
batchSize := 1000
for {
result := db.Model(&User{}).
Where("processed = ?", false).
Limit(batchSize).
Update("processed", true)
if result.RowsAffected == 0 {
break
}
time.Sleep(100 * time.Millisecond)
}
return nil
}
golang-migrate
migrate -path ./migrations -database "mysql://user:pass@tcp(localhost:3306)/db" up
goose
goose mysql "user:pass@tcp(localhost:3306)/db" up
GORM 自带
适合简单场景,复杂迁移建议用专业工具。
完整的迁移系统
package migration
import (
"gorm.io/gorm"
)
type Migration struct {
Version string
Name string
Up func(*gorm.DB) error
Down func(*gorm.DB) error
}
type MigrationRecord struct {
ID uint
Version string
ExecutedAt time.Time
}
func Migrate(db *gorm.DB, migrations []Migration) error {
if err := db.AutoMigrate(&MigrationRecord{}); err != nil {
return err
}
for _, m := range migrations {
var record MigrationRecord
result := db.Where("version = ?", m.Version).First(&record)
if result.Error == nil {
continue
}
if err := m.Up(db); err != nil {
return err
}
db.Create(&MigrationRecord{
Version: m.Version,
ExecutedAt: time.Now(),
})
}
return nil
}
使用示例
func main() {
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
migrations := []migration.Migration{
{
Version: "20240101000001",
Name: "create_users",
Up: func(db *gorm.DB) error {
return db.Migrator().CreateTable(&User{})
},
Down: func(db *gorm.DB) error {
return db.Migrator().DropTable(&User{})
},
},
}
migration.Migrate(db, migrations)
}
数据库迁移要谨慎。版本化管理、可回滚、零停机是核心原则。生产环境使用专业迁移工具,开发环境可以用 AutoMigrate 提高效率。