inkmouse

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

处理一个循环数据,需要频繁开启数据库事务操作,为了以便于事务的回滚和提交,在循环内部创建一个匿名函数

  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 变量,可彻底解决事务状态误判问题。此方案保证:

  1. 每个设备的事务独立处理,错误互不影响。
  2. defer 中的 err 准确反映当前事务的错误状态。
  3. 日志清晰记录每个设备的操作结果,便于排查问题。

匿名函数机制
(1) 参数传递的本质

  1. 每次循环迭代时,外部的 v 会作为参数传递给匿名函数的 imei。
  2. imei 是当前迭代中 v 的副本,二者值相同但内存地址独立。
  3. 匿名函数内部操作的是 imei,与外部的 v 完全隔离。

(2) 闭包陷阱的规避
直接使用外部变量 v 的问题:

for _, v := range imeis {
    func() {  // ❌ 错误写法:直接捕获外部 v
        data := dbs.ReadFromRedis(v) // 所有匿名函数共享同一个 v
    }()
}

由于 Go 协程的调度不确定性,所有匿名函数可能读取到循环结束后的最终 v 值。
参数传递是解决此问题的标准做法。

posted on 2025-03-06 10:03  末日晨星  阅读(17)  评论(0)    收藏  举报