golang 事务
tx对象
tx, err := db.Begin()
tx.Exec(query1)
tx.Exec(query2)
tx.commit()
一般查询使用的是db对象的方法,事务则是使用另外一个对象。
tx, err := db.Begin()
db.Exec(query1)
tx.Exec(query2)
tx.commit()
事务与连接
创建Tx对象的时候,会从连接池中取出连接,然后调用相关的Exec方法的时候,连接仍然会绑定在改事务处理中。在实际的事务处理中,go可能创建不同的连接,但是那些其他连接都不属于该事务。例如上面例子中db创建的连接和tx的连接就不是一回事。
事务的连接生命周期从Beigin函数调用起,直到Commit和Rollback函数的调用结束。事务也提供了prepare语句的使用方式,但是需要使用Tx.Stmt方法创建。prepare设计的初衷是多次执行,对于事务,有可能需要多次执行同一个sql。然而无论是正常的prepare和事务处理,prepare对于连接的管理都有点小复杂。因此私以为尽量避免在事务中使用prepare方式。例如下面例子就容易导致错误:
tx, _ := db.Begin() defer tx.Rollback() stmt, _ tx.Prepare("INSERT ...") defer stmt.Close() tx.Commit()
事务并发
对于sql.Tx对象,因为事务过程只有一个连接,事务内的操作都是顺序执行的,在开始下一个数据库交互之前,必须先完成上一个数据库交互。例如下面的例子:
rows, _ := db.Query("SELECT id FROM user") for rows.Next() { var mid, did int rows.Scan(&mid) db.QueryRow("SELECT id FROM detail_user WHERE master = ?", mid).Scan(&did) }
调用了Query方法之后,在Next方法中取结果的时候,rows是维护了一个连接,再次调用QueryRow的时候,db会再从连接池取出一个新的连接。rows和db的连接两者可以并存,并且相互不影响。
可是,这样逻辑在事务处理中将会失效:
rows, _ := tx.Query("SELECT id FROM user") for rows.Next() { var mid, did int rows.Scan(&mid) tx.QueryRow("SELECT id FROM detail_user WHERE master = ?", mid).Scan(&did) }
实践
func doSomething(){ panic("A Panic Running Error") } func clearTransaction(tx *sql.Tx){ err := tx.Rollback() if err != sql.ErrTxDone && err != nil{ log.Fatalln(err) } } func main() { db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?parseTime=true") if err != nil { log.Fatalln(err) } defer db.Close() tx, err := db.Begin() if err != nil { log.Fatalln(err) } defer clearTransaction(tx) rs, err := tx.Exec("UPDATE user SET gold=50 WHERE real_name='vanyarpy'") if err != nil { log.Fatalln(err) } rowAffected, err := rs.RowsAffected() if err != nil { log.Fatalln(err) } fmt.Println(rowAffected) rs, err = tx.Exec("UPDATE user SET gold=150 WHERE real_name='noldorpy'") if err != nil { log.Fatalln(err) } rowAffected, err = rs.RowsAffected() if err != nil { log.Fatalln(err) } fmt.Println(rowAffected) doSomething() if err := tx.Commit(); err != nil { // tx.Rollback() 此时处理错误,会忽略doSomthing的异常 log.Fatalln(err) } }
我们定义了一个clearTransaction(tx)函数,该函数会执行rollback操作。因为我们事务处理过程中,任何一个错误都会导致main函数退出,因此在main函数退出执行defer的rollback操作,回滚事务和释放连接。
如果不添加defer,只在最后Commit后check错误err后再rollback,那么当doSomething发生异常的时候,函数就退出了,此时还没有执行到tx.Commit。这样就导致事务的连接没有关闭,事务也没有回滚。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
2018-11-27 Centos上执行Shell的四种方式
2018-11-27 Linux定时任务出现问题时正确的解决步骤