Shmulik Klein

Software Engineer @ JetBrains | Munich, Germany


Phantom Menace

In this second post of the series of “Transaction Isolation”, I would like to discuss about the committed read isolation level and its risks.


...

	ctx1 := context.Background()
	tx1, err := db.BeginTx(ctx1, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
	if err != nil {
		log.Fatalln(err)
	}
	printUsers(tx1)

	go func() {
		ctx2 := context.Background()
		tx2, err := db.BeginTx(ctx2, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
		if err != nil {
			log.Fatalln(err)
			tx2.Rollback()
			return
		}
		_, err = tx2.ExecContext(ctx2, "INSERT INTO users(name) VALUES('Elmo')")
		if err != nil {
			log.Fatalln(err)
			tx1.Rollback()
			return
		}
		tx2.Commit()
	}()

	time.Sleep(1 * time.Second)
	printUsers(tx1)
	tx1.Rollback()
}

func printUsers(tx *sql.Tx) {
	r, err := tx.Query("SELECT name FROM users")
	if err != nil {
		log.Fatalln(err)
		tx.Rollback()
		return
	}
	defer r.Close()
	var name string
	fmt.Print("tx1 sees the following users: ")
	for r.Next() {
		r.Scan(&name)
		fmt.Printf(" %s ", name)
	}
	fmt.Print("\n")
}

When running the code, we see that the second transaction, tx2, being isolated with a COMMITED_READ level, sees the inconsistent values for the users. In the first SELECT query users, it sees Bert and Ernie, but on the second query, it sees Bert, Ernie and Elmo.