這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Transaction 事務
交易處理是資料的重要特性。尤其是對於一些支付系統,事務保證性對商務邏輯會有重要影響。golang的mysql驅動也封裝好了事務相關的操作。我們已經學習了db的Query和Exec方法處理查詢和修改資料庫。
tx對象
一般查詢使用的是db對象的方法,事務則是使用另外一個對象。sql.Tx對象。使用db的Begin方法可以建立tx對象。tx對象也有資料庫互動的Query,Exec和Prepare方法。用法和db的相關用法類似。查詢或修改的操作完畢之後,需要調用tx對象的Commit提交或者Rollback方法復原。
一旦建立了tx對象,交易處理都依賴與tx對象,這個對象會從串連池中取出一個閒置串連,接下來的sql執行都基於這個串連,直到commit或者rollback調用之後,才會把串連釋放到串連池。
在交易處理的時候,不能使用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語句沒有關係。
事務與串連
建立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對象,因為事務過程只有一個串連,事務內的操作都是順序執行的,在開始下一個資料庫互動之前,必須先完成上一個資料庫互動。例如下面的例子:
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
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。