深圳幻海软件技术有限公司 欢迎您!

Golang transaction 事务使用的正确姿势

2023-02-28

本文中作者展示了 golang 事务的三种写法。第一种写法这种写法非常朴实,程序流程也非常明确,但是事务处理与程序流程嵌入太深,容易遗漏,造成严重的问题复制func DoSomething() (err error) { &nbs

本文中作者展示了 golang 事务的三种写法

第一种写法

这种写法非常朴实,程序流程也非常明确,但是事务处理与程序流程嵌入太深,容易遗漏,造成严重的问题

func DoSomething() (err error) {
    tx, err := db.Begin()
    if err != nil {
        return
    }


    defer func() {
        if p := recover(); p != nil {
            tx.Rollback()
            panic(p)  // re-throw panic after Rollback
        }
    }()


    if _, err = tx.Exec(...); err != nil {
        tx.Rollback()
        return
    }
    if _, err = tx.Exec(...); err != nil {
        tx.Rollback()
        return
    }
    // ...


    err = tx.Commit()
    return
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

第二种写法

下面这种写法把事务处理从程序流程抽离了出来,不容易遗漏,但是作用域是整个函数,程序流程不是很清晰

func DoSomething() (err error) {
    txerr := db.Begin()
    if err != nil {
        return
    }


    defer func() {
        if p := recover(); p != nil {
            tx.Rollback()
            panic(p// re-throw panic after Rollback
        } else if err != nil {
            tx.Rollback()
        } else {
            err = tx.Commit()
        }
    }()


    if _err = tx.Exec(...); err != nil {
        return
    }
    if _err = tx.Exec(...); err != nil {
        return
    }
    // ...
    return
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

第三种写法

写法三是对写法二的进一步封装,写法高级一点,缺点同上

func Transact(db *sql.DBtxFunc func(*sql.Txerror) (err error) {
    txerr := db.Begin()
    if err != nil {
        return
    }


    defer func() {
        if p := recover(); p != nil {
            tx.Rollback()
            panic(p// re-throw panic after Rollback
        } else if err != nil {
            tx.Rollback()
        } else {
            err = tx.Commit()
        }
    }()


    err = txFunc(tx)
    return err
}


func DoSomething() error {
    return Transact(dbfunc (tx *sql.Txerror {
        if _err := tx.Exec(...); err != nil {
            return err
        }
        if _err := tx.Exec(...); err != nil {
            return err
        }
    })
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.

我的写法

经过总结和实验,我采用了下面这种写法,defer tx.Rollback() 使得事务回滚始终得到执行。当 tx.Commit() 执行后,tx.Rollback() 起到关闭事务的作用, 当程序因为某个错误中止,tx.Rollback() 起到回滚事务,同事关闭事务的作用。

普通场景

func DoSomething() (err error) {
  tx_ := db.Begin()
  defer tx.Rollback()

  if _err = tx.Exec(...); err != nil {
      return
  }
  if _err = tx.Exec(...); err != nil {
      return
  }
  // ...


  err = tx.Commit()
  return
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

循环场景

(1) 小事务 每次循环提交一次 在循环内部使用这种写法的时候,defer 不能使用,所以要把事务部分抽离到独立的函数当中

func DoSomething() (err error) {
    tx_ := db.Begin()
    defer tx.Rollback()

    if _err = tx.Exec(...); err != nil {
        return
    }
    if _err = tx.Exec(...); err != nil {
        return
    }
    // ...


    err = tx.Commit()
    return
}


for {
    if err := DoSomething(); err != nil{
         // ...
    }
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

(2) 大事务 批量提交 大事务的场景和普通场景是一样的,没有任何区别

func DoSomething() (err error) {
    tx_ := db.Begin()
    defer tx.Rollback()

    for{
        if _err = tx.Exec(...); err != nil {
            return
        }
        if _err = tx.Exec(...); err != nil {
            return
        }
        // ...
    }

    err = tx.Commit()
    return
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.