处理大量数据时,逐条操作效率太低。批量操作能显著提升性能,但也有一些注意事项。
插入多条记录:
users := []User{
{Name: "用户1", Age: 20},
{Name: "用户2", Age: 25},
{Name: "用户3", Age: 30},
}
db.Create(&users)
GORM 会生成一条 INSERT 语句,包含所有数据。
数据量大时,单条 SQL 可能超出数据库限制。用 CreateInBatches 分批:
users := make([]User, 10000)
for i := range users {
users[i] = User{Name: fmt.Sprintf("用户%d", i), Age: i % 100}
}
db.CreateInBatches(&users, 100)
每批 100 条,生成多条 INSERT 语句。
分批大小建议:
max_allowed_packet 是 4MB更新符合条件的所有记录:
db.Model(&User{}).Where("role = ?", "guest").Update("role", "member")
更新多个字段:
db.Model(&User{}).Where("age < ?", 18).Updates(map[string]interface{}{
"status": "minor",
"role": "limited",
})
删除符合条件的所有记录:
db.Where("status = ?", "inactive").Delete(&User{})
软删除模型同样适用:
db.Where("last_login < ?", time.Now().AddDate(-1, 0, 0)).Delete(&User{})
批量操作默认不触发钩子:
func (u *User) BeforeCreate(tx *gorm.DB) error {
fmt.Println("创建前钩子")
return nil
}
users := []User{{Name: "用户1"}, {Name: "用户2"}}
db.Create(&users)
钩子不会被调用。
要触发钩子,需要逐条操作或使用迭代:
for _, user := range users {
db.Create(&user)
}
性能会下降,但钩子会执行。
大批量操作应该放在事务中:
err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.CreateInBatches(&users, 100).Error; err != nil {
return err
}
if err := tx.Model(&User{}).Where("age < ?", 18).Update("status", "minor").Error; err != nil {
return err
}
return nil
})
事务保证原子性,失败时回滚。
超大数据量时,原生 SQL 更高效:
db.Exec(`
INSERT INTO users (name, age, created_at)
VALUES (?, ?, NOW())
`, "测试", 20)
批量插入用 UNION ALL:
db.Exec(`
INSERT INTO users (name, age)
SELECT ?, ? UNION ALL SELECT ?, ?
`, "用户1", 20, "用户2", 25)
或者用数据库特有的批量语法:
// MySQL
db.Exec(`
INSERT INTO users (name, age) VALUES
('用户1', 20),
('用户2', 25),
('用户3', 30)
`)
// PostgreSQL
db.Exec(`
INSERT INTO users (name, age) VALUES
($1, $2), ($3, $4), ($5, $6)
`, "用户1", 20, "用户2", 25, "用户3", 30)
MySQL 的 LOAD DATA INFILE:
db.Exec(`
LOAD DATA LOCAL INFILE '/path/to/users.csv'
INTO TABLE users
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
(name, age)
`)
PostgreSQL 的 COPY:
db.Exec(`
COPY users(name, age) FROM '/path/to/users.csv' DELIMITER ',' CSV
`)
这是最快的导入方式,适合初始化大量数据。
以插入 10000 条记录为例:
| 方式 | 耗时 |
|---|---|
| 逐条插入 | 30s+ |
| 单条 INSERT 多值 | 0.5s |
| CreateInBatches(100) | 0.8s |
| LOAD DATA INFILE | 0.1s |
差距明显,选择合适的方式很重要。
查询大量数据时,避免一次性加载到内存:
rows, err := db.Model(&User{}).Rows()
if err != nil {
panic(err)
}
defer rows.Close()
for rows.Next() {
var user User
db.ScanRows(rows, &user)
// 处理 user
}
流式处理,内存占用恒定。
大数据量处理时分批进行:
pageSize := 1000
page := 0
for {
var users []User
result := db.Offset(page * pageSize).Limit(pageSize).Find(&users)
if result.Error != nil || len(users) == 0 {
break
}
for _, user := range users {
// 处理 user
}
page++
}
内存溢出
一次性加载太多数据:
var users []User
db.Find(&users)
数据量大时内存爆炸。用分批查询或流式处理。
超时
批量操作耗时太长:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
db.WithContext(ctx).CreateInBatches(&users, 100)
设置合理的超时时间。
锁等待
大批量更新可能阻塞其他操作:
db.Transaction(func(tx *gorm.DB) error {
return tx.Model(&User{}).Where("1=1").Update("status", "inactive").Error
})
分批处理,减少单次锁定范围。
批量操作是性能优化的关键。选择合适的批次大小,配合事务和流式处理,能高效处理大量数据。