defer, Commit and Rollback

使用 transaction 的時候,利用 defer 函式的特性 commit 或 rollback 比較合於 Go 的慣例。理論上應該有兩種形式,第一種是利用 Commit() 之後 Rollback() 不會真正執行的特性:

tx, err = db.Begin()
if err != nil {
	return err
}
defer tx.Rollback()

if _, err := tx.Exec(query); err != nil {
	return err
}

tx.Commit()

另外一種則是用 panicrecover 來確認在 defer 執行時的狀態是出錯還是正常

tx, err = db.Begin()
if err != nil {
	return
}
defer func() {
	if e := recover(); e != nil {
		tx.Rollback()
		err = e
	} else {
		tx.Commit()
	}
}

if _, err = tx.Exec(query); err != nil {
	panic("Cannot execute sql query")
}
return

後者看起來比較潮,但實際測速的結果

func BenchmarkDeferRollback(b *testing.B) {
	fn := func(i int) {
		tx, err := db.Begin()
		if err != nil {
			b.Fatalf("Cannot begin transaction: %s", err)
		}
		defer tx.Rollback()
		q := tx.Stmt(newPadQuery)
		q.Exec(u.ID, fmt.Sprintf("rollback%d", i), "content")
		tx.Commit()
	}
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fn(i)
	}
}

func BenchmarkDeferIfElse(b *testing.B) {
	fn := func(i int) {
		tx, err := db.Begin()
		if err != nil {
			b.Fatalf("Cannot begin transaction: %s", err)
		}
		defer func () {
			if err := recover(); err != nil {
				tx.Rollback()
			} else {
				tx.Commit()
			}
		}()
		q := tx.Stmt(newPadQuery)
		q.Exec(u.ID, fmt.Sprintf("if-else%d", i), "content")
	}
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		fn(i)
	}
}
BenchmarkDeferRollback-8          100000             17449 ns/op
BenchmarkDeferIfElse-8            100000             17816 ns/op

我跑了大概十來次測試,都是直接 Rollback 比較快,雖然只有快幾 % 而已。猜測原因,可能是 panic 跟 recover 這兩個錯誤處理函式耗去了一些效能吧。

Comments