处理一个循环数据,需要频繁开启数据库事务操作,为了以便于事务的回滚和提交,在循环内部创建一个匿名函数
func(str string) {
tx := db.Begin()
// 定义局部err变量
var err error
defer func() {
if r := recover(); r != nil {
tx.Rollback()
logrus.Errorf("事务回滚(Panic): IMEI=%s, 错误: %v", str, r)
} else if err != nil {
tx.Rollback()
logrus.Errorf("事务回滚(错误): IMEI=%s, 错误: %v", str, err)
} else {
if commitErr := tx.Commit().Error; commitErr != nil {
logrus.Errorf("事务提交失败: IMEI=%s, 错误: %v", str, commitErr)
}
}
}()
}
}(v)
通过将每个设备的事务封装到独立作用域,并确保 defer 使用局部 err 变量,可彻底解决事务状态误判问题。此方案保证:
- 每个设备的事务独立处理,错误互不影响。
- defer 中的 err 准确反映当前事务的错误状态。
- 日志清晰记录每个设备的操作结果,便于排查问题。
匿名函数机制
(1) 参数传递的本质
- 每次循环迭代时,外部的 v 会作为参数传递给匿名函数的 imei。
- imei 是当前迭代中 v 的副本,二者值相同但内存地址独立。
- 匿名函数内部操作的是 imei,与外部的 v 完全隔离。
(2) 闭包陷阱的规避
直接使用外部变量 v 的问题:
for _, v := range imeis {
func() { // ❌ 错误写法:直接捕获外部 v
data := dbs.ReadFromRedis(v) // 所有匿名函数共享同一个 v
}()
}
由于 Go 协程的调度不确定性,所有匿名函数可能读取到循环结束后的最终 v 值。
参数传递是解决此问题的标准做法。
浙公网安备 33010602011771号