[golang]MySQL中如何為單個事務設定隔離級別

一桶冷水發表於2018-03-07

相比起簡單的鎖表,事務提供了更好的併發效能,但同時也帶來更大的複雜性,如隔離級別,mvcc,死鎖等。網上關於事務隔離級別的介紹遍地都是,就不再贅述了。

MySQL提供了3個等級的隔離級別配置,下面分別列出配置方法:

  1. 全域性
    直接改配置檔案
    set global transaction isolation level repeatable read;
  2. 當前session
    set tx_isolation = 'repeatable-read';
    set session transaction isolation level repeatable read;
  3. 下一個事務
    set transaction isolation level repeatable read;

但是在Go的MySQL驅動是自帶連線池的,這使得這3個等級都無法直接使用,畢竟誰知道下一條sql會跑在哪個連線上呢?那麼有什麼解決方案呢?

  1. Go1.8以後的版本,sql庫新增了API BeginTx,直接呼叫即可
tx0, err := db.BeginTx(context.Background(), &sql.TxOptions{
	Isolation: sql.LevelReadUncommitted,
})
複製程式碼
  1. 如果是1.8以前的版本,可以利用事務在一個session上處理的特性hack一下
tx1, err := db.Begin()
_, err = tx1.Exec("ROLLBACK")
_, err = tx1.Exec("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED")
_, err = tx1.Exec("BEGIN")
rows, err := tx1.Query("SELECT COUNT(*) FROM USER")
rows.Close()
tx1.Commit()
複製程式碼
  1. 或者也可以使用不同的配置建立兩個db物件,每個物件有自己獨立的連線池
db0, err := sql.Open("mysql", "root@/test")
db1, err := sql.Open("mysql", "root@/test?tx_isolation='read-uncommitted'")
複製程式碼

最後提供一段測試程式碼,可以很清楚的看到read-uncommitted帶來的髒讀問題。

func main() {
	db, err := sql.Open("mysql", "root@/test")
        if err != nil {
		panic(err)
	}
	db1, err := sql.Open("mysql", "root@/test?tx_isolation='read-uncommitted'")
	if err != nil {
		panic(err)
	}

	tx0, err := db.Begin()
	if err != nil {
		panic(err)
	}

	_, err = tx0.Exec("insert into user value (null,?)", "cc")
	if err != nil {
		panic(err)
	}

	rows, err := db1.Query("select count(*) from user")
	if err != nil {
		panic(err)
	}
	for rows.Next() {
		s := 0
		err = rows.Scan(&s)
		if err != nil {
			panic(err)
		}
		fmt.Println(s)
	}
	tx0.Rollback()
}
複製程式碼

參考

  • https://github.com/go-sql-driver/mysql/issues/472

相關文章