Postgres
建立**Postgres
** Container 與 simple_bank
database.
docker run --name postgres -p 5432:5432 -e POSTGRES_USER=root -e POSTGRES_PASSWORD=secret --network bank-network -d postgres:14-alpine
docker exec -it postgres createdb --username=root --owner=root simple_bank
Connect to Postgres
and access simple_bank
database
docker exec -it postgres psql -U root simple_bank
Get current isolation level in Postgres
Postgres
**預設都是Read committed
show transaction isolation level
; 這個SQL指令時,所獲得的隔離級別是針對當前的console session Level**Repeatable Read
**Postgres
無法修改Global Session ****isolation level
simple_bank=# show transaction isolation level;
transaction_isolation
-----------------------
read committed
(1 row)
Change isolation level in Postgres
MySQL
我們在建立transactions前可以改變isolation level
Postgres
只有建立了transactions 才可以改變isolation level
-- Tx1:
simple_bank> begin;
BEGIN
simple_bank> set transaction isolation level read uncommitted;
SET
simple_bank=*# show transaction isolation level;
transaction_isolation
-----------------------
read uncommitted
(1 row)
建立accounts
、entries
、transfers
Table
CREATE TABLE "accounts" (
"id" bigserial PRIMARY KEY,
"owner" varchar NOT NULL,
"balance" bigint NOT NULL,
"currency" varchar NOT NULL,
"created_at" timestamptz NOT NULL DEFAULT (now())
);
CREATE TABLE "entries" (
"id" bigserial PRIMARY KEY,
"account_id" bigint NOT NULL,
"amount" bigint NOT NULL,
"created_at" timestamptz NOT NULL DEFAULT (now())
);
CREATE TABLE "transfers" (
"id" bigserial PRIMARY KEY,
"from_account_id" bigint NOT NULL,
"to_account_id" bigint NOT NULL,
"amount" bigint NOT NULL,
"created_at" timestamptz NOT NULL DEFAULT (now())
);
CREATE INDEX ON "accounts" ("owner");
CREATE UNIQUE INDEX ON "accounts" ("owner", "currency");
CREATE INDEX ON "entries" ("account_id");
CREATE INDEX ON "transfers" ("from_account_id");
CREATE INDEX ON "transfers" ("to_account_id");
CREATE INDEX ON "transfers" ("from_account_id", "to_account_id");
COMMENT ON COLUMN "entries"."amount" IS 'can be negative or positive';
COMMENT ON COLUMN "transfers"."amount" IS 'must be positive';
ALTER TABLE "entries" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id");
ALTER TABLE "transfers" ADD FOREIGN KEY ("from_account_id") REFERENCES "accounts" ("id");
ALTER TABLE "transfers" ADD FOREIGN KEY ("to_account_id") REFERENCES "accounts" ("id");
插入三筆Account的資訊進到accounts
INSERT INTO "accounts" ("owner", "balance", "currency") VALUES
('one', 100, 'USD'),
('two', 100, 'USD'),
('three', 100, 'USD');
-- Tx1:
simple_bank=*# select * from accounts;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
1 | one | 100 | USD | 2023-08-23 11:03:23.441965+00
2 | two | 100 | USD | 2023-08-23 11:03:23.441965+00
3 | three | 100 | USD | 2023-08-23 11:03:23.441965+00
(3 rows)
Read uncommitted
isolation level in Postgres建立兩個Console與Transaction
,並設定兩個Session的Isolation Level為**Read Uncommitted
**
-- Tx1:
simple_bank=# begin;
BEGIN
simple_bank=*# set transaction isolation level read uncommitted;
SET
simple_bank=*# show transaction isolation level;
transaction_isolation
-----------------------
read uncommitted
(1 row)
-- Tx2:
simple_bank=# begin;
BEGIN
simple_bank=*# set transaction isolation level read uncommitted;
SET
simple_bank=*# show transaction isolation level;
transaction_isolation
-----------------------
read uncommitted
(1 row)
在Tx1
中對accounts
table進行select query
-- Tx1
simple_bank=*# select * from accounts;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
1 | one | 100 | USD | 2023-08-23 11:03:23.441965+00
2 | two | 100 | USD | 2023-08-23 11:03:23.441965+00
3 | three | 100 | USD | 2023-08-23 11:03:23.441965+00
(3 rows)
在Tx2
中對accounts
table進行id=1的 query
-- Tx2
simple_bank=*# select * from accounts where id = 1;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
1 | one | 100 | USD | 2023-08-23 11:03:23.441965+00
(1 row)
這時候在Tx1
中對Account 1的balance減去10
-- Tx1
simple_bank=*# update accounts set balance = balance - 10 where id = 1 returning *;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
1 | one | 90 | USD | 2023-08-23 11:03:23.441965+00
(1 row)
UPDATE 1
我們在Tx2
再次對Account 1進行Query
Read uncommitted
**隔離級別,但結果仍然是100美元。Read uncommitted
與Read committed
**行為相同。Read committed
**。Read uncommitted
**隔離級別並不建議使用。-- Tx2
simple_bank=*# select * from accounts where id = 1;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
1 | one | 100 | USD | 2023-08-23 11:03:23.441965+00
(1 row)
對Tx1
進行 commit
後,我們在Tx2
再次查詢Account 1,這時就可以看見Balance的修改
--Tx1
simple_bank=*# commit;
COMMIT
--Tx2
simple_bank=*# select * from accounts where id = 1;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
1 | one | 90 | USD | 2023-08-23 11:03:23.441965+00
(1 row)
simple_bank=*# commit;
COMMIT
Read committed
isolation level in Postgres建立兩個Console和Transaction
,並設定兩個Session的Isolation Level為**Read Committed
**
-- Tx1:
simple_bank=# begin;
BEGIN
simple_bank=*# set transaction isolation level read committed;
SET
simple_bank=*# show transaction isolation level;
transaction_isolation
-----------------------
read committed
(1 row)
-- Tx2:
simple_bank=# begin;
BEGIN
simple_bank=*# set transaction isolation level read committed;
SET
simple_bank=*# show transaction isolation level;
transaction_isolation
-----------------------
read committed
(1 row)
在Tx1
中對accounts
table進行select query
-- Tx1
simple_bank=*# select * from accounts;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
2 | two | 100 | USD | 2023-08-23 11:03:23.441965+00
3 | three | 100 | USD | 2023-08-23 11:03:23.441965+00
1 | one | 90 | USD | 2023-08-23 11:03:23.441965+00
(3 rows)
在Tx2
中對accounts
table進行id=1的查詢並進行balance≥90的Query
dirty read
現象外,還希望了解phantom read
的處理方式。-- Tx2
simple_bank=*# select * from accounts where id = 1;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
1 | one | 90 | USD | 2023-08-23 11:03:23.441965+00
(1 row)
simple_bank=*# select * from accounts where balance >= 90;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
2 | two | 100 | USD | 2023-08-23 11:03:23.441965+00
3 | three | 100 | USD | 2023-08-23 11:03:23.441965+00
1 | one | 90 | USD | 2023-08-23 11:03:23.441965+00
(3 rows)
這時候在Tx1
中對Account 1的balance減去10
-- Tx1
simple_bank=*# update accounts set balance = balance - 10 where id = 1 returning *;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
1 | one | 80 | USD | 2023-08-23 11:03:23.441965+00
(1 row)
UPDATE 1
simple_bank=*# select * from accounts where id = 1;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
1 | one | 80 | USD | 2023-08-23 11:03:23.441965+00
(1 row)
我們在Tx2
再次對Account 1進行Query
Tx1
尚未committed
,所以Tx2
的Account 1 Query 依舊是90read-committed
可以避免dirty read
-- Tx2
simple_bank=*# select * from accounts where id = 1;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
1 | one | 90 | USD | 2023-08-23 11:03:23.441965+00
(1 row)
將Tx1
進行commit 並在Tx2
再次對Account 1進行Query
Tx2
中的balance已經被更新non-repeatable
-- Tx1
simple_bank=*# commit;
COMMIT
-- Tx2
simple_bank=*# select * from accounts where id = 1;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
1 | one | 80 | USD | 2023-08-23 11:03:23.441965+00
(1 row)
我們在Tx2
再次進行balance ≥90的Query
phantom read
同樣我們在Tx2
進行同一個Query取得balance 90以上的Accounts,可以發現相同的Query但是Return 的Record 數量不相同,這就被稱為phantom read
-- Tx2
simple_bank=*# select * from accounts where balance >= 90;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
2 | two | 100 | USD | 2023-08-23 11:03:23.441965+00
3 | three | 100 | USD | 2023-08-23 11:03:23.441965+00
(2 rows)
simple_bank=*# commit;
COMMIT
所以read-committed
只能避免dirty read
無法避開non-repeatable
、phantom read
Repeatable
read isolation level in Postgres建立兩個Console和Transaction
,並設定兩個Session的Isolation Level為**Repeatable
**
-- Tx1:
simple_bank=# begin;
BEGIN
simple_bank=*# set transaction isolation level repeatable read;
SET
simple_bank=*# show transaction isolation level;
transaction_isolation
-----------------------
repeatable read
(1 row)
-- Tx2:
simple_bank=# begin;
BEGIN
simple_bank=*# set transaction isolation level repeatable read;
SET
simple_bank=*# show transaction isolation level;
transaction_isolation
-----------------------
repeatable read
(1 row)
在Tx1
中對accounts
table進行select query
-- Tx1
simple_bank=*# select * from accounts;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
2 | two | 100 | USD | 2023-08-23 11:03:23.441965+00
3 | three | 100 | USD | 2023-08-23 11:03:23.441965+00
1 | one | 80 | USD | 2023-08-23 11:03:23.441965+00
(3 rows)
在Tx2
中對accounts
table進行id=1的查詢與balance ≥80 的Query
-- Tx2
simple_bank=*# select * from accounts where id = 1;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
1 | one | 80 | USD | 2023-08-23 11:03:23.441965+00
(1 row)
simple_bank=*# select * from accounts where balance >= 80;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
2 | two | 100 | USD | 2023-08-23 11:03:23.441965+00
3 | three | 100 | USD | 2023-08-23 11:03:23.441965+00
1 | one | 80 | USD | 2023-08-23 11:03:23.441965+00
(3 rows)
這時候在Tx1
中對Account 1的balance減去10
read committed
」中已經阻止了dirty read
, 所以就不需要再驗證-- Tx1
simple_bank=*# update accounts set balance = balance - 10 where id = 1 returning *;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
1 | one | 70 | USD | 2023-08-23 11:03:23.441965+00
(1 row)
UPDATE 1
simple_bank=*# select * from accounts;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
2 | two | 100 | USD | 2023-08-23 11:03:23.441965+00
3 | three | 100 | USD | 2023-08-23 11:03:23.441965+00
1 | one | 70 | USD | 2023-08-23 11:03:23.441965+00
(3 rows)
simple_bank=*# commit;
COMMIT
我們在Tx2
再次對Account 1進行查詢,來確認是否可以獲得Tx1
所更新的balance
Tx1
已經將其更改為70並且成功commit
。repeatable-read
isolation level 確保所有的讀取查詢都是repeatable
,這意味著,它總是返回相同的結果,即使其他已提交的交易進行了更改 (避免non-repeatable
)。phantom read
)。-- Tx2
simple_bank=*# select * from accounts where id = 1;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
1 | one | 80 | USD | 2023-08-23 11:03:23.441965+00
(1 row)
simple_bank=*# select * from accounts where balance >= 80;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
2 | two | 100 | USD | 2023-08-23 11:03:23.441965+00
3 | three | 100 | USD | 2023-08-23 11:03:23.441965+00
1 | one | 80 | USD | 2023-08-23 11:03:23.441965+00
(3 rows)
如果我們在Tx2
對Account 1 的balance那結果會是如何?
simple_bank=*# update accounts set balance = balance - 10 where id = 1 returning *;
ERROR: could not serialize access due to concurrent update
simple_bank=!# rollback;
ROLLBACK
建立兩個Console和Transaction
,並設定兩個Session的Isolation Level為**Repeatable
**
-- Tx1:
simple_bank=# begin;
BEGIN
simple_bank=*# set transaction isolation level repeatable read;
SET
simple_bank=*# show transaction isolation level;
transaction_isolation
-----------------------
repeatable read
(1 row)
-- Tx2:
simple_bank=# begin;
BEGIN
simple_bank=*# set transaction isolation level repeatable read;
SET
simple_bank=*# show transaction isolation level;
transaction_isolation
-----------------------
repeatable read
(1 row)
現在想像我們有一個使用情境,需要計算所有帳戶的餘額總和,然後用該總餘額建立一個新的帳戶
-- Tx1:
simple_bank=*# select sum(balance) from accounts;
sum
-----
270
(1 row)
總合為270美元,然後我們在帳戶表中插入一個新的記錄,其中擁有者為"sum",餘額為270,且貨幣為"USD"。
-- Tx1:
simple_bank=*# insert into accounts(owner, balance, currency) values ('sum', 270, 'USD') returning *;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
4 | sum | 270 | USD | 2023-08-23 11:46:02.890059+00
(1 row)
INSERT 0 1
simple_bank=*# select * from accounts;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
2 | two | 100 | USD | 2023-08-23 11:03:23.441965+00
3 | three | 100 | USD | 2023-08-23 11:03:23.441965+00
1 | one | 70 | USD | 2023-08-23 11:03:23.441965+00
4 | sum | 270 | USD | 2023-08-23 11:46:02.890059+00
(4 rows)
但是如果也想在Tx2
中對accounts
table進行這項操作呢?
repeatable-read
隔離級別,Tx2
中的select查詢只會看到原始的帳戶列表,而不會看到交易1剛剛插入的新記錄。-- Tx2
simple_bank=*# select * from accounts;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
2 | two | 100 | USD | 2023-08-23 11:03:23.441965+00
3 | three | 100 | USD | 2023-08-23 11:03:23.441965+00
1 | one | 70 | USD | 2023-08-23 11:03:23.441965+00
(3 rows)
simple_bank=*# select sum(balance) from accounts;
sum
-----
270
(1 row)
simple_bank=*# insert into accounts(owner, balance, currency) values ('sum', 270, 'USD') returning *;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
4 | sum | 270 | USD | 2023-08-23 11:46:02.890059+00
(1 row)
我們再對Tx1
和Tx2
進行Commit,這時Tx2
的Select會出現在duplicate sum records
Serialization anomaly
**Serialization
**運行的,一個接一個,那麼我們不可能有兩條總和為270的記錄。Tx1
或Tx2
先運行,我們應該有一條270美元的記錄,和另一條540美元的記錄。-- Tx1
simple_bank=*# commit;
COMMIT
-- Tx2
simple_bank=*# commit;
COMMIT
simple_bank=*# select * from accounts;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
2 | two | 100 | USD | 2023-08-23 11:03:23.441965+00
3 | three | 100 | USD | 2023-08-23 11:03:23.441965+00
1 | one | 70 | USD | 2023-08-23 11:03:23.441965+00
4 | sum | 270 | USD | 2023-08-23 11:46:02.890059+00
5 | sum | 270 | USD | 2023-08-23 11:46:02.890059+00
(5 rows)
所以Postgres 在repeatable-read
隔離級別中無法避免**Serialization anomaly
**
建立兩個Console和Transaction
,並設定兩個Session的Isolation Level為Serializable
-- Tx1:
simple_bank=# begin;
BEGIN
simple_bank=*# set transaction isolation level serializable;
SET
simple_bank=*# show transaction isolation level;
transaction_isolation
-----------------------
serializable
(1 row)
-- Tx2:
simple_bank=# begin;
BEGIN
simple_bank=*# set transaction isolation level serializable;
SET
simple_bank=*# show transaction isolation level;
transaction_isolation
-----------------------
serializable
(1 row)
在Tx1
中對accounts
table進行select query 並進行Sum
和Insert
-- Tx1
simple_bank=*# select * from accounts;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
2 | two | 100 | USD | 2023-08-23 11:03:23.441965+00
3 | three | 100 | USD | 2023-08-23 11:03:23.441965+00
1 | one | 70 | USD | 2023-08-23 11:03:23.441965+00
4 | sum | 270 | USD | 2023-08-23 11:46:02.890059+00
7 | sum | 270 | USD | 2023-08-23 12:15:16.939807+00
(5 rows)
simple_bank=*# select sum(balance) from accounts;
sum
-----
810
(1 row)
simple_bank=*# insert into accounts(owner, balance, currency) values ('sum2', 810, 'USD') returning *;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
10 | sum | 810 | USD | 2023-08-23 12:19:23.223866+00
(1 row)
INSERT 0 1
simple_bank=*# select * from accounts;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
2 | two | 100 | USD | 2023-08-23 11:03:23.441965+00
3 | three | 100 | USD | 2023-08-23 11:03:23.441965+00
1 | one | 70 | USD | 2023-08-23 11:03:23.441965+00
4 | sum | 270 | USD | 2023-08-23 11:46:02.890059+00
7 | sum | 270 | USD | 2023-08-23 12:15:16.939807+00
10 | sum | 810 | USD | 2023-08-23 12:19:23.223866+00
(6 rows)
在Tx2
中對accounts
table進行select query 並進行Sum
和Insert
simple_bank=*# select * from accounts;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
2 | two | 100 | USD | 2023-08-23 11:03:23.441965+00
3 | three | 100 | USD | 2023-08-23 11:03:23.441965+00
1 | one | 70 | USD | 2023-08-23 11:03:23.441965+00
4 | sum | 270 | USD | 2023-08-23 11:46:02.890059+00
7 | sum | 270 | USD | 2023-08-23 12:15:16.939807+00
(5 rows)
simple_bank=*# select sum(balance) from accounts;
sum
-----
810
(1 row)
simple_bank=*# insert into accounts(owner, balance, currency) values ('sum3', 810, 'USD') returning *;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
11 | sum | 810 | USD | 2023-08-23 12:17:07.013085+00
(1 row)
INSERT 0 1
simple_bank=*# select * from accounts;
id | owner | balance | currency | created_at
----+-------+---------+----------+-------------------------------
2 | two | 100 | USD | 2023-08-23 11:03:23.441965+00
3 | three | 100 | USD | 2023-08-23 11:03:23.441965+00
1 | one | 70 | USD | 2023-08-23 11:03:23.441965+00
4 | sum | 270 | USD | 2023-08-23 11:46:02.890059+00
7 | sum | 270 | USD | 2023-08-23 12:15:16.939807+00
11 | sum | 810 | USD | 2023-08-23 12:17:07.013085+00
(6 rows)
並對Tx1
和 Tx2
進行Commit
Tx1
成功提交。Tx2
因交易之間的讀/寫依賴關係而拋出錯誤。concurrent
的交易不再創建重複的記錄。dependencies detection
機制來防止serializable anomaly
。-- Tx1:
simple_bank=# commit;
COMMIT
-- Tx2:
simple_bank> 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.