Skip to content

Instantly share code, notes, and snippets.

@saward
Last active August 29, 2015 14:02
Show Gist options
  • Save saward/682859013f2b80f9eecc to your computer and use it in GitHub Desktop.
Save saward/682859013f2b80f9eecc to your computer and use it in GitHub Desktop.
Serializable vs repeatable read
package iso
import (
"database/sql"
"errors"
"fmt"
_ "github.com/lib/pq"
"runtime"
"strings"
"testing"
"time"
)
var db, err = sql.Open("postgres", "user=<put_user_here> dbname=<give_some_table> password=<your_pass> sslmode=disable")
var attempts = 500
func TestUpdates(t *testing.T) {
runtime.GOMAXPROCS(runtime.NumCPU())
if err != nil {
t.Fatal(err)
}
_, err = db.Exec("CREATE TABLE stest (statistic_id int, hits int)")
if err != nil {
t.Errorf(fmt.Sprintf("%s", err))
}
var s int
err = db.QueryRow("INSERT INTO stest (statistic_id, hits) VALUES (1,0) returning statistic_id").Scan(&s)
if err != nil {
t.Errorf(fmt.Sprintf("%s", err))
}
fin := make(chan bool, 0)
for i := 0; i < attempts; i++ {
go func() {
p_func := func() (interface{}, error) {
txn, err := BeginIsolation("serializable")
if err != nil {
if txn != nil {
txn.Rollback()
}
return nil, err
}
_, err = txn.Exec("UPDATE stest SET hits = hits + 1 WHERE statistic_id=$1", s)
if err != nil {
txn.Rollback()
return nil, err
}
txn.Commit()
return nil, nil
}
_, err := IsolatedRerun(p_func)
if err != nil {
t.Errorf(fmt.Sprintf("%s", err))
}
fin <- true
}()
}
for i := 0; i < attempts; i++ {
<-fin
}
var count int
err = db.QueryRow("SELECT hits FROM stest WHERE statistic_id=$1", s).Scan(&count)
if err != nil {
t.Errorf(fmt.Sprintf("%s", err))
}
fmt.Printf("Final count: %d\n", count)
_, _ = db.Exec("DROP TABLE stest")
db.Close()
}
func BeginIsolation(isolation string) (*sql.Tx, error) {
txn, err := db.Begin()
if err != nil {
return nil, err
}
_, err = txn.Exec(fmt.Sprintf("SET TRANSACTION ISOLATION LEVEL %s", isolation))
if err != nil {
return nil, err
}
var t_level string
err = txn.QueryRow("select current_setting('transaction_isolation')").Scan(&t_level)
if err != nil {
return nil, err
}
if t_level != isolation {
return nil, errors.New("Isolation level was not successfully enabled")
}
return txn, err
}
func IsolatedRerun(f func() (interface{}, error)) (interface{}, error) {
finished := false
var res_obj interface{}
var err error
for !finished {
res_obj, err = f()
if err != nil {
if !strings.Contains(fmt.Sprintf("%s", err), "The transaction might succeed if retried") && !strings.Contains(fmt.Sprintf("%s", err), "could not serialize access due to read/write dependencies among transactions") && !strings.Contains(fmt.Sprintf("%s", err), "could not serialize access due to concurrent update") && !strings.Contains(fmt.Sprintf("%s", err), "deadlock detected") {
return nil, err
}
time.Sleep(10 * time.Millisecond)
} else {
finished = true
}
}
return res_obj, nil
}
@saward
Copy link
Author

saward commented Jun 16, 2014

With serializable:
Final count: 486
Final count: 478
Final count: 484
Final count: 477

With repeatable read:
Final count: 500
Final count: 500
Final count: 500
Final count: 500

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment