事务处理是数据的重要特性。尤为是对于一些支付系统,事务保证性对业务逻辑会有重要影响。golang的mysql驱动也封装好了事务相关的操做。咱们已经学习了db的Query和Exec方法处理查询和修改数据库。mysql
通常查询使用的是db对象的方法,事务则是使用另一个对象。sql.Tx对象。使用db的Begin方法能够建立tx对象。tx对象也有数据库交互的Query,Exec和Prepare方法。用法和db的相关用法相似。查询或修改的操做完毕以后,须要调用tx对象的Commit提交或者Rollback方法回滚。golang
一旦建立了tx对象,事务处理都依赖与tx对象,这个对象会从链接池中取出一个空闲的链接,接下来的sql执行都基于这个链接,直到commit或者rollback调用以后,才会把链接释放到链接池。sql
在事务处理的时候,不能使用db的查询方法,虽而后者能够获取数据,但是这不属于同一个事务处理,将不会接受commit和rollback的改变,一个简单的事务例子以下:数据库
tx, err := db.Begin() tx.Exec(query1) tx.Exec(query2) tx.commit()
在tx中使用db是错误的:并发
tx, err := db.Begin() db.Exec(query1) tx.Exec(query2) tx.commit()
上述代码在调用db的Eexc方法的时候,tx会绑定链接到事务中,db则是额外的一个链接,二者不是同一个事务。须要注意,Begin和Commit方法,与sql语句中的BEGIN或COMMIT语句没有关系。tcp
建立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()
由于stmt.Close使用defer语句,即函数退出的时候再清理stmt,但是实际执行过程的时候,tx.Commit就已经释放了链接。当函数退出的时候,再执行stmt.Close的时候,链接可能有被使用了。设计
对于sql.Tx对象,由于事务过程只有一个链接,事务内的操做都是顺序执行的,在开始下一个数据库交互以前,必须先完成上一个数据库交互。例以下面的例子:code
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) }
tx执行了Query方法后,链接转移到rows上,在Next方法中,tx.QueryRow将尝试获取该链接进行数据库操做。由于尚未调用rows.Close,所以底层的链接属于busy状态,tx是没法再进行查询的。上面的例子看起来有点傻,毕竟涉及这样的操做,使用query的join语句就能规避这个问题。例子只是为了说明tx的使用问题。
前面对事务解释了一堆,说了那么多,其实还不如share的code。下面就事务的使用作简单的介绍。由于事务是单个链接,所以任何事务处理过程的出现了异常,都须要使用rollback,一方面是为了保证数据完整一致性,另外一方面是释放事务绑定的链接。
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。这样就致使事务的链接没有关闭,事务也没有回滚。
database/sql提供了事务处理的功能。经过Tx对象实现。db.Begin会建立tx对象,后者的Exec和Query执行事务的数据库操做,最后在tx的Commit和Rollback中完成数据库事务的提交和回滚,同时释放链接。
tx事务环境中,只有一个数据库链接,事务内的Eexc都是依次执行的,事务中也可使用db进行查询,可是db查询的过程会新建链接,这个链接的操做不属于该事务。
关于database/sql和mysql的驱动,咱们已经分三部份内容介绍了。下一节,将会对以前的内容进行梳理总结,包括错误处理和注意事项的补充。
做者:人世间连接:http://www.jianshu.com/p/bc8120bec94e來源:简书著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。