Last active
August 29, 2015 14:02
-
-
Save saward/682859013f2b80f9eecc to your computer and use it in GitHub Desktop.
Serializable vs repeatable read
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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