批量操作是数据库性能优化的重头戏。一条一条插入和批量插入,性能可能差几十倍。
GORM 的 Create 方法支持批量插入:
users := []User{
{Name: "张三", Email: "zhangsan@example.com"},
{Name: "李四", Email: "lisi@example.com"},
{Name: "王五", Email: "wangwu@example.com"},
}
// 批量插入
db.Create(&users)
GORM 会生成一条 INSERT 语句插入所有记录。
数据量大时,一次插入太多会出问题:
GORM 提供了分批插入的方式:
users := make([]User, 10000)
// ... 填充数据
// 每批 100 条
db.CreateInBatches(users, 100)
CreateInBatches 会把数据分成多批,每批执行一条 INSERT。
做个简单测试:
// 单条插入
for _, user := range users {
db.Create(&user)
}
// 批量插入
db.Create(&users)
// 分批插入
db.CreateInBatches(users, 100)
插入 1000 条数据:
差距明显。批量插入省去了每次建立连接、解析 SQL、写日志的开销。
GORM 的 Updates 支持批量更新:
// 更新所有记录的某个字段
db.Model(&User{}).Where("status = ?", "pending").Update("status", "active")
// 根据 ID 批量更新
db.Model(&User{}).Where("id IN ?", ids).Update("status", "active")
// 更新多个字段
db.Model(&User{}).Where("id IN ?", ids).Updates(User{Status: "active", UpdatedAt: time.Now()})
注意批量更新不会触发钩子函数,如果需要钩子,要用 Save 或 Update:
// 会触发 BeforeUpdate、AfterUpdate
for _, id := range ids {
var user User
db.First(&user, id)
user.Status = "active"
db.Save(&user)
}
但这样又回到了循环操作,性能差。需要权衡业务需求。
// 批量删除
db.Where("status = ?", "inactive").Delete(&User{})
// 根据 ID 批量删除
db.Where("id IN ?", ids).Delete(&User{})
同样,批量删除不触发钩子。软删除模型要注意:
// 软删除模型,批量 Delete 只是设置 deleted_at
db.Where("id IN ?", ids).Delete(&User{})
// 真正删除
db.Unscoped().Where("id IN ?", ids).Delete(&User{})
Upsert 是 INSERT ON CONFLICT 的简称,存在则更新,不存在则插入:
// MySQL: ON DUPLICATE KEY UPDATE
// PostgreSQL: ON CONFLICT
user := User{Name: "张三", Email: "zhangsan@example.com"}
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "email"}},
DoUpdates: clause.AssignmentColumns([]string{"name"}),
}).Create(&user)
批量 Upsert:
users := []User{...}
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "email"}},
DoUpdates: clause.AssignmentColumns([]string{"name", "updated_at"}),
}).Create(&users)
Upsert 比先查再决定插入或更新效率高很多。
批量操作时要注意内存:
// 从文件读取数据批量插入
file, _ := os.Open("users.csv")
reader := csv.NewReader(file)
batchSize := 100
batch := make([]User, 0, batchSize)
for {
record, err := reader.Read()
if err == io.EOF {
break
}
batch = append(batch, User{Name: record[0], Email: record[1]})
if len(batch) >= batchSize {
db.Create(&batch)
batch = batch[:0] // 清空,复用底层数组
}
}
// 处理剩余数据
if len(batch) > 0 {
db.Create(&batch)
}
这种方式内存占用稳定,不会因为文件大小而变化。
对于超大数据量,原生 SQL 可能更快:
// MySQL 的 LOAD DATA INFILE
db.Exec(`LOAD DATA LOCAL INFILE 'users.csv'
INTO TABLE users
FIELDS TERMINATED BY ','
(name, email)`)
// PostgreSQL 的 COPY
db.Exec(`COPY users(name, email) FROM '/path/to/users.csv' DELIMITER ',' CSV`)
这些数据库特有的批量导入命令,比 INSERT 快一个数量级。
批量操作放在事务里,要么全成功,要么全回滚:
err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.CreateInBatches(users, 100).Error; err != nil {
return err
}
if err := tx.Where("status = ?", "pending").Update("status", "processed").Error; err != nil {
return err
}
return nil
})
但事务太大也有问题:锁持有时间长、回滚代价大、主从延迟。大数据量操作考虑分批提交。
批量操作的核心原则:
养成批量思维,代码性能会有质的提升。