Skip to main content

Command Palette

Search for a command to run...

Isolation Level: Serializable V.S. Repeatable Read

Practice On PostgreSQL Database

Updated
•3 min read•View as Markdown

前置作業:在資料庫建立資料表和資料

docker exec -it pgacid psql -U postgres

postgres=# create table alphabets(id serial primary key, a text);
CREATE TABLE

// 一次新增一筆
postgres=# insert into alphabets(a) values('a');
INSERT 0 1

// 一次新增多筆
postgres=# insert into alphabets(a) values('a'),('a'),('b'),('b');
INSERT 0 4

postgres=# select * from alphabets;
 id | a 
----+---
  1 | a
  2 | a
  3 | a
  4 | b
  5 | b
(5 rows)

**預期目標:tx1先執行,a -> b,tx2 後執行,b -> a

Isolation Level: Repeatable Read

打開兩個terminal,開始兩個tx,設定isolation level repeatable read

tx1: a -> b

postgres=# begin transaction isolation level repeatable read;
BEGIN

postgres=*# update alphabets set a = 'b' where a = 'a';
UPDATE 3
postgres=*# select * from alphabets;
 id | a 
----+---
  4 | b
  5 | b
  1 | b
  2 | b
  3 | b
(5 rows)

postgres=*# commit;
COMMIT

tx2: b -> a

postgres=# begin transaction isolation level repeatable read;
BEGIN

postgres=*# update alphabets set a = 'a' where a = 'b';
UPDATE 2

postgres=*# select * from alphabets;
 id | a 
----+---
  1 | a
  2 | a
  3 | a
  4 | a
  5 | a
(5 rows)

postgres=*# commit;
COMMIT

兩個tx都完成,分別在兩個terminal看看最後結果如何?

tx1:

postgres=# select * from alphabets;
 id | a 
----+---
  1 | b
  2 | b
  3 | b
  4 | a
  5 | a
(5 rows)

tx2:

postgres=# select * from alphabets;
 id | a 
----+---
  1 | b
  2 | b
  3 | b
  4 | a
  5 | a
(5 rows)

結果不如預期,alphabets table的a欄位資料,沒有全部都變成a!

要如何達到預期的結果?

Isolation Level: Serializable

同時開始tx1, tx2,並設定isolation level serializable

tx1:

postgres=# begin transaction isolation level serializable;
BEGIN

tx2:

postgres=# begin transaction isolation level serializable;
BEGIN

tx1, tx2 同時執行各自的任務

tx1: a -> b

postgres=*# update alphabets set a = 'b' where a = 'a';
UPDATE 2

tx2: b -> a

postgres=*# update alphabets set a = 'a' where a = 'b';
UPDATE 2

tx1 先 commit,並看結果如何?確實順利把 a -> b

postgres=*# commit;
COMMIT

postgres=# select * from alphabets;
 id | a 
----+---
  3 | b
  4 | b
  1 | b
  2 | b
(4 rows)

tx2 commit並看結果如何?出現 ERROR!

postgres=*# commit;
ERROR:  could not serialize access due to read/write dependencies among transactions
DETAIL:  Reason code: Canceled on identification as a pivot, during commit attempt.
HINT:  The transaction might succeed if retried.

WHY?

根據PostgreSQL官方文件,從PostgreSQL version 9.1開始有Documentation of Serializable Snapshot Isolation (簡稱SSI) ,當兩個tx同時進行寫入資料,其中一個先commit,另一個就會被迫rollback,再重新執行,這是SSI的保護機制

如何讓tx2正確執行,達到預期的結果(b -> a)?

rollback tx2,再次執行,就能得到預期的結果

postgres=# rollback;
WARNING:  there is no transaction in progress
ROLLBACK

postgres=# begin transaction isolation level serializable;
BEGIN

postgres=*# update alphabets set a = 'a' where a = 'b';
UPDATE 4

postgres=*# commit;
COMMIT

postgres=# select * from alphabets;
 id | a 
----+---
  3 | a
  4 | a
  1 | a
  2 | a
(4 rows)

Reference:

Udemy Course: Fundamentals of Database Engineering 15

SSI - PostgreSQL wiki

More from this blog

My Backend Notes

24 posts

從經濟系轉職的菜鳥後端工程師,因為很菜很菜,所以把所有的學習筆記都記錄在這裡