✅ 场景 1:批量更新多条不同数据
假设你有一批不同的数据项,需要根据主键(ID)逐条更新每个记录的不同字段值。这时可以利用批量更新的 SQL 语句,通过 gorm
的 Updates()
方法实现批量更新。
示例代码:
func (r *LiveEnvDataItem) BatchUpdate(ctx context.Context, items []LiveEnvDataItem) error {
orm, err := resource.GetGorm(resource.MySQLLiveQA)
if err != nil {
resource.LoggerService.Warning(ctx, fmt.Sprintf("get orm fail, err: %s", err))
return err
}
tx := orm.Begin() // 开启事务
for _, item := range items {
err := tx.Model(&LiveEnvDataItem{}).Where("id = ?", item.ID).Updates(item).Error
if err != nil {
tx.Rollback()
resource.LoggerService.Warning(ctx, fmt.Sprintf("update db data fail: err:%+v", err))
return err
}
}
if err := tx.Commit().Error; err != nil {
resource.LoggerService.Warning(ctx, fmt.Sprintf("commit transaction fail: err:%+v", err))
return err
}
return nil
}
📝 说明:
- 使用事务
tx := orm.Begin()
确保批量更新的原子性,避免部分更新成功、部分失败的情况。 - 遍历
items
,根据主键更新每条记录。 - 如果任意一条更新失败,回滚事务。
✅ 场景 2:批量更新相同字段
如果你需要批量更新多条记录的相同字段值,可以直接使用 Where
条件结合 Updates()
方法来实现。
示例代码:
func (r *LiveEnvDataItem) BatchUpdateField(ctx context.Context, ids []int64, updateFields map[string]interface{}) error {
orm, err := resource.GetGorm(resource.MySQLLiveQA)
if err != nil {
resource.LoggerService.Warning(ctx, fmt.Sprintf("get orm fail, err: %s", err))
return err
}
err = orm.Model(&LiveEnvDataItem{}).Where("id IN ?", ids).Updates(updateFields).Error
if err != nil {
resource.LoggerService.Warning(ctx, fmt.Sprintf("update db datas fail: err:%+v", err))
return err
}
return nil
}
📝 说明:
ids
是要更新的记录主键 ID 的列表。updateFields
是要批量更新的字段和值的映射,例如:
updateFields := map[string]interface{}{
"status": 1,
"updated_at": time.Now(),
}
这样就能一次性更新多条记录的相同字段值。
✅ 场景 3:批量更新多条数据,不同字段值
如果你有多条记录,每条记录的字段值都不一样,可以构建批量更新的 SQL 查询。这种情况下,你需要用到 CASE
表达式 来动态构建 SQL。
示例代码:
func (r *LiveEnvDataItem) BatchUpdateDynamic(ctx context.Context, items []LiveEnvDataItem) error {
orm, err := resource.GetGorm(resource.MySQLLiveQA)
if err != nil {
resource.LoggerService.Warning(ctx, fmt.Sprintf("get orm fail, err: %s", err))
return err
}
sql := "UPDATE live_env_data_items SET status = CASE"
ids := []int64{}
for _, item := range items {
sql += fmt.Sprintf(" WHEN id = %d THEN %d", item.ID, item.Status)
ids = append(ids, item.ID)
}
sql += " END WHERE id IN ?"
err = orm.Exec(sql, ids).Error
if err != nil {
resource.LoggerService.Warning(ctx, fmt.Sprintf("update db datas fail: err:%+v", err))
return err
}
return nil
}
📝 说明:
- 通过
CASE WHEN
动态构建批量更新 SQL。 ids
用于指定批量更新的记录范围。
✅ 场景 4:使用 GORM 的 Save()
方法批量更新
如果你的数据项包含主键,使用 Save()
方法也可以批量更新。
示例代码:
func (r *LiveEnvDataItem) BatchSave(ctx context.Context, items []LiveEnvDataItem) error {
orm, err := resource.GetGorm(resource.MySQLLiveQA)
if err != nil {
resource.LoggerService.Warning(ctx, fmt.Sprintf("get orm fail, err: %s", err))
return err
}
err = orm.Save(&items).Error
if err != nil {
resource.LoggerService.Warning(ctx, fmt.Sprintf("save db datas fail: err:%+v", err))
return err
}
return nil
}
📝 说明:
Save()
会根据主键判断记录是否存在,存在则更新,不存在则插入。- 使用
Save()
方法时要注意性能问题,适合小批量更新。
✅ 对比总结
场景 | 方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|---|
批量更新多条不同数据 | tx.Updates() + 事务 | 每条记录的更新字段不同 | 安全性高,支持事务 | 遍历更新,性能一般 |
批量更新相同字段 | Where().Updates() | 批量更新相同字段值 | 简单高效 | 只适用于相同字段的更新 |
动态批量更新 | CASE WHEN + 动态 SQL | 每条记录的更新字段不同 | 高效,SQL 一次性执行 | SQL 拼接复杂 |
批量保存 | Save() | 小批量更新或新增记录 | 简单易用 | 不适合大批量数据更新 |
✅ 最佳实践推荐
- 小批量数据:使用事务 +
Updates()
,保证数据更新的原子性。 - 大批量相同字段更新:使用
Where().Updates()
。 - 大批量不同字段更新:构建
CASE WHEN
动态 SQL,提升效率。 - 新增或更新混合操作:使用
Save()
。
选择合适的方案,取决于你的业务需求和性能要求。如果数据量较大,建议尽量采用批量 SQL,减少数据库的多次交互,从而提升性能。
case then 怎么理解
你提到的这段代码是用来批量更新多条数据的不同字段值的,它动态生成了一条带有 CASE WHEN
表达式的 SQL 语句。
下面我详细拆解这个 SQL 语句的含义,并解释为什么要用这种方式来批量更新。
💡 这段 SQL 的生成逻辑解释
sql := "UPDATE live_env_data_items SET status = CASE"
ids := []int64{}
for _, item := range items {
sql += fmt.Sprintf(" WHEN id = %d THEN %d", item.ID, item.Status)
ids = append(ids, item.ID)
}
sql += " END WHERE id IN ?"
最终生成的 SQL 示例
假设 items
包含以下数据:
ID | Status |
---|---|
1 | 2 |
2 | 3 |
3 | 4 |
则生成的 SQL 语句类似于:
UPDATE live_env_data_items
SET status = CASE
WHEN id = 1 THEN 2
WHEN id = 2 THEN 3
WHEN id = 3 THEN 4
END
WHERE id IN (1, 2, 3);
SQL 语句分解
-
UPDATE live_env_data_items
- 表示要更新
live_env_data_items
表的数据。
- 表示要更新
-
SET status = CASE
- 表示要更新
status
字段,接下来的CASE WHEN
表达式用于为每个id
设置不同的status
值。
- 表示要更新
-
WHEN id = 1 THEN 2
- 表示当
id
等于 1 时,将status
字段更新为 2。
- 表示当
-
WHEN id = 2 THEN 3
- 表示当
id
等于 2 时,将status
字段更新为 3。
- 表示当
-
WHEN id = 3 THEN 4
- 表示当
id
等于 3 时,将status
字段更新为 4。
- 表示当
-
END WHERE id IN (1, 2, 3)
- 限定条件,表示只更新
id
为 1、2、3 的记录。
- 限定条件,表示只更新
🎯 为什么要用 CASE WHEN
批量更新?
在实际业务中,批量更新多条数据时,可能每条记录的更新字段值不同,如果逐条执行 UPDATE
操作,性能很差。而 CASE WHEN
表达式允许你用一条 SQL 语句来实现批量更新,避免了多次执行 UPDATE
,从而提升性能。
两种方式的对比
方式 | SQL 示例 | 优缺点 |
---|---|---|
逐条更新 | UPDATE live_env_data_items SET status = 2 WHERE id = 1; UPDATE live_env_data_items SET status = 3 WHERE id = 2; UPDATE live_env_data_items SET status = 4 WHERE id = 3; | 缺点:需要执行多次 SQL,效率低。 |
CASE WHEN 批量更新 | UPDATE live_env_data_items SET status = CASE WHEN id = 1 THEN 2 WHEN id = 2 THEN 3 WHEN id = 3 THEN 4 END WHERE id IN (1, 2, 3); | 优点:一次性执行批量更新,性能更好。 |
✅ 适用场景
CASE WHEN
批量更新适用于每条记录的更新值不同的情况。例如:
-
订单状态更新
不同订单的状态更新为不同值。 -
用户权限更新
不同用户的权限级别更新为不同值。
💻 改进代码
可以优化一下 SQL 拼接逻辑,避免手动拼接字符串,改用更安全的占位符:
优化后的代码:
func (r *LiveEnvDataItem) BatchUpdateDynamic(ctx context.Context, items []LiveEnvDataItem) error {
orm, err := resource.GetGorm(resource.MySQLLiveQA)
if err != nil {
resource.LoggerService.Warning(ctx, fmt.Sprintf("get orm fail, err: %s", err))
return err
}
// 动态构建 CASE WHEN SQL
sql := "UPDATE live_env_data_items SET status = CASE"
var ids []int64
var cases []string
for _, item := range items {
cases = append(cases, fmt.Sprintf("WHEN id = %d THEN %d", item.ID, item.Status))
ids = append(ids, item.ID)
}
sql += " " + strings.Join(cases, " ") + " END WHERE id IN ?"
// 执行 SQL
err = orm.Exec(sql, ids).Error
if err != nil {
resource.LoggerService.Warning(ctx, fmt.Sprintf("update db datas fail: err:%+v", err))
return err
}
return nil
}
🧩 总结
CASE WHEN
批量更新 可以用一条 SQL 语句更新多个不同记录的字段值,避免多次执行UPDATE
,从而提升数据库操作的效率。- 适用场景:订单状态更新、用户权限更新、配置项更新等。
- 性能优势:批量更新时性能更好,减少数据库交互次数。