事務處理是數據的重要特性。尤爲是對於一些支付系統,事務保證性對業務邏輯會有重要影響。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來源:簡書著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。