事务本身是一个很有用的东西,合理的利用可以很好的解决问题,但是如果使用不合理,也可能造成其他问题。本文就Go语言的常用姿势来展开讨论。

1、使用Golang语言的ORM举例

grom是支持事务的.

GORM 默认会将单个的 create, update, delete操作封装在事务内进行处理,以确保数据的完整性。

如果你想把多个 create, update, delete 操作作为一个原子操作,Transaction 就是用来完成这个的。

// 开启事务
tx := db.Begin()

// 在事务中执行具体的数据库操作 (事务内的操作使用 'tx' 执行,而不是 'db')
tx.Create(...)

// ...

// 如果发生错误则执行回滚
tx.Rollback()

// 或者(未发生错误时)提交事务
tx.Commit()

特别注意:delete支持事务回滚;truncate不支持回滚

tx.Rollback().Error  可以查看回滚是否报错

delete如果不加where会把所有数据清空,如果此时有另一个mysql实例往数据库新插入数据,当前mysql实例也无法执行回滚

2、展开讨论

做事务的核心逻辑是处理报错和异常,缺一不可。事务进行过程中,如果因为异常或报错程序中断,是不会自动回滚的,所以一定要保证,在接收异常和报错前就回滚。

1、一般的操作

        这种写法程序流程也明确,但是事务处理与程序流程嵌入太深,容易遗漏;

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
}

2、高级写法

把数据库操作进一步封装,写法高级一点,也同时处理了异常和报错

func Transact(db *sql.DB, txFunc func(*sql.Tx) error) (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
        } else if err != nil {
            tx.Rollback()
        } else {
            err = tx.Commit()
        }
    }()


    err = txFunc(tx)
    return err
}


func DoSomething() error {
    return Transact(db, func (tx *sql.Tx) error {
        if _, err := tx.Exec(...); err != nil {
            return err
        }
        if _, err := tx.Exec(...); err != nil {
            return err
        }
    })
}