7. Transaction Isolation Level
Isolation Level 是用來做 Transaction Concurrent Control 的設定,來管理一個
Transaction 會對其他 Concurrent Transaction 造成什麼影響,首先要先了解四個在
Concurrent Transaction 可能會發生的現象,每個 Isolation level 分別則是保證讓這
些不能發生。
四個現象:
Dirty Read A transaction reads data written by a concurrent uncommitted transaction.
Nonrepeatable Read A transaction re-reads data it has previously read and finds that data has been
modified by another transaction
Phantom Read A transaction re-executes a query returning a set of rows that satisfy a search
condition and finds that the set of rows satisfying the condition has changed due to
another recently-committed transaction.
Serialization Anomaly The result of successfully committing a group of transactions is inconsistent with all
possible orderings of running those transactions one at a time.
8. Transaction Isolation Level
四個 Isolation level 則是保證:
以我們的情況,在檢查是否場地有被佔用時,不能有 Phantom Read 的現象,所以我
們要選擇 Serializable Transaction 來處理這段業務邏輯。
然而在使用 Repeatable Read 跟 Serialization 這兩個等級時,要特別注意的是一旦
資料庫發現 Transaction 有可能會違反它們的保證時將會結束該 Transaction 並返
回 Error,所以我們的業務邏輯也必須做好 Transaction 失敗的準備
Isolation level Dirty Read Nonrepeatable
Read
Phantom Read Serialization
Anomaly
Read
Uncommitted
Possible Possible Possible Possible
Read
Committed
Not Possible Possible Possible Possible
Repeatable
Read
Not Possible Not Possible Possible Possible
Serializable Not Possible Not Possible Not Possible Not Possible
9. Transaction
所以最終我們的完整 SQL 操作則是:
BEGIN TRANSACTION LEVEL SERIALIZABLE;
SELECT count(1)
FROM reservations
WHERE field_id = ${my_field_id}
AND state != ‘可借用’
AND date = ${my_date}
AND ${my_start_at} < end_at
AND ${my_end_at} > start_at;
-- if count == 0
INSERT INTO reservations (field_id, state, date, start_at, end_at, ...) VALUES
(${my_field_id}, ‘使用者佔用’, ${my_date}, ${my_start_at}, ${my_end_at});
INSERT INTO applications (...) VALUES (...);
COMMIT;
12. Worker
在我們的需求中,需要寄信給審核者或是申請者通知他們各種事項。由於寄信的時間
太長,不適合放在一個 Http Request 裡面完成,所以會做成 Job 放在額外的 Worker
來做完。這個 Worker 可以是一個 cronjob 或是 while loop 去 DB 存取需要被處理的
job 來做,或是我們可以把 job 放進 queue 裡面去,由 worker 來消化。
在 Job 實作有一些地方需要考量:
1. Job 的狀態管理,以我們寄信的例子來說,我們會在意:
a. 是否等待被寄信?
b. 是否已經被 worker 處理中?
c. 是否已寄送成功?
d. 為什麼寄送失敗?
e. 是否需要重新寄送?
13. Worker
在 while loop worker 以及 consumer worker 的實作上則需要特別注意對外連線的
Error handling 與 Exception 處理,否則 Worker 可能會死掉或是沒在做事。
通常這兩類的 worker 應該要是長這樣子的結構:
while (true) {
try {
db_connection = db.connect();
// fetch jobs from db or mq and change the state of jobs and then do jobs.
} catch (err) {
log(err);
}
}
注意如上,對外部的連線應該要包含在 while 裡面,這樣子一來如果對外部連線壞掉
可以重新 loop 進來再建立連線,否則的話很可能 worker 對外部連線斷掉之後他就
不斷 loop 但是不做事。