資料庫的 Transaction
微起個頭
先看一下基本操作


從資料庫拿資料
更新資料庫的資料
Transaction 是什麼

把幾個 query 合起來作為一個單位
---> 一個 transaction
Transaction 是什麼
例如甲轉帳 100 元給乙
begin
read(A)
A = A - 100
write(A)
read(B)
B = B + 100
write(B)
commit
T1
Transaction 是什麼
例如更新用戶資料
begin
write(A)
write(B)
write(C)
...
commit
T1
Transaction 的特性
Atomicity (all or none)
Concistency (資料保持某種一致性或正確性)
Isolation (transaction 之間的隔離)
Durability (持久性)
ACID
除了 Atomicity 資料庫一定會保證全部成功或全部失敗之外
其它的會因為做法不同而有不同程度的分別
Durability
commit 成功的資料更動
會持續存在
不太會因為系統掛掉資料就沒有了
單個硬碟不被認為是 stable storage
但是比起主記憶體
他可以承受沒電很久
也算是有某種程度的 durability ?
資料要寫到 stable storage 才算成功
information residing in stable storage is never lost
Atomicity
在同一個 transaction 裡面的指令
最後會全部成功或全部失敗
如果寫完 A 正要去寫 B 的時候
斷電了
怎麼辦
要去做之前,先記錄下來
這樣即使出意外 還有紀錄可以查
而且要記錄到 disk,這種紀錄檔叫 log
Isolation
讓 transaction 不太需要擔心別的 transaction 做了什麼
沒有 isolation 的話可能會有怪怪的結果
begin A = A - 100 B = B + 100 commit |
begin A = A * 1.05 B = B * 1.05 commit |
T1 T2
schedule
總和不變
總和 * 1.05
Isolation
如果不要 concurrency 的話
一個 transaction 完才處理另外一個
效能很差 也浪費資源
同時間能處理的 query 會少很多
cpu、i/o 不會同時被利用
簡單的 transaction 要等耗時間的 transaction
Consistency
比較模糊
跟另外 3 個不在同一個層次
應該是說資料對於 application 來說 邏輯上的正確性/一致性
然後這個正確性不會因為 transaction 的關係被破壞
Isolation
1. 怎樣的 schedule 是 ok 的,怎樣的是不 ok 的
2. 怎麼讓不 ok 的 schedule 不要都 commit 成功
怎樣的 schedule ok
begin A = A - 100 B = B + 100 commit |
begin A = A * 1.05 B = B * 1.05 commit |
T1 T2
begin A = A - 100 B = B + 100 commit |
begin A = A * 1.05 B = B * 1.05 commit |
T1 T2
來討論 schedule 的 serializability
沒有干擾
有干擾
serial schedule
總和不變
總和 * 1.05
換成 read 跟 write
read(A) write(A) read(B) write(B) |
read(A) write(A) read(B) write(B) |
T1 T2
read(A) write(A) read(B) write(B) |
read(A) write(A) read(B) write(B) |
T1 T2
是不是 non-serial 就不行
read(A) write(A) |
read(B) write(B) |
T1 T2
不同目標的話就沒關係
serializable
read(A) write(A) |
read(B) write(B) |
T1 T2
≡
serial schedule
是不是同一個目標只要有交錯就不行
read(A) read(A) |
read(A) |
T1 T2
如果都是 read 就沒關係
serializable
read(A) read(A) |
read(A) |
T1 T2
≡
serial schedule
感覺不行交換的例子
read(A) write(A) |
write(A) |
T1 T2
conflict & non-conflict operations
兩個 operations 針對同一個目標
至少其中一個是 write
這樣他們就是 conflict operations
read(A) write(A) |
write(A) |
T1 T2
conflict-equivalent schedules
一個 schedule
可以透過交換 non-conflict operations
轉為另一個 schedule 的話
他們兩個就是 conflict-equivalent
read(A) write(A) |
read(B) write(B) |
T1 T2
read(A) write(A) |
read(B) write(B) |
T1 T2
≡
conflict-serializable
一個 schedule
如果可以透過交換 non-conflict operations
轉為另一個 serial schedule 的話
他就是 conflict-serializable
read(A) write(A) |
read(B) write(B) |
T1 T2
read(A) write(A) |
read(B) write(B) |
T1 T2
conflict serializable
≡
serial schedule
一個不錯的計算 schedule 是否為 conflict serializable 的方法
畫 dependency graph
以 transaction 為點
conflict operation 為邊
先的 transaction 指向後的 transaction
T1
T2
dependency graph
T1
T2
read(A) write(A) |
read(B) write(B) |
T1 T2
read(A) read(A) |
read(A) |
T1 T2
dependency graph
T1
T2
write(A) |
read(A) write(A) |
T1 T2
dependency graph
T1
T2
read(A) write(A) |
write(A) |
T1 T2
有 cycle 的 schedule 就不是 conflict-serializable
沒有的就是
serializable
Serializable means that concurrency has added no effect.
Unserializable schedules introduce inconsistencies.
不能讓 non-serializable 的 schedule 成功
1. 事前預防
2. 發生的時候 abort / rollback
1 事前預防
lock 的機制
要先拿到 lock 才能做事
沒拿到就要等
begin read(A) ... |
begin write(A) ... |
T1 T2
一個基本的 lock 方式
shared lock & exclusive lock
shared | exclusive | |
shared | O | X |
exclusive | X | X |
begin read(A) ... |
begin write(A) ... |
T1 T2
read 前要先拿 shared lock
write 前要先拿 exclusive lock
2 發生 non-serializable schedule 的時候 abort / rollback
不擋操作 但記下操作的時間
偵測到有 non-serializable schedule 的時候
abort / rollback 其中一個或多個 transaction
其實只允許 serializable 的 schedule 的話
大多數時候效能還是太爛了
在對資料更新的時候
其他人都不能讀
或是一直有人讀的話 就一直不能更新
isolation levels
- serializable
- repeatable read
- read committed
- read uncommited
對同一個目標讀兩次
結果不會被別的 transaction 改變 嗎
4 個 level 都不允許 dirty writes
如果有一個目標已經被更新了但還沒 commit 或 rollback
其它 transaction 不能去更新
看起來好像只有 read uncommitted
可以解決資料在更新的時候不能讀
或是有人讀的時候其他人不能更新的狀況?
Multiple versions
讓多個版本的資料同時存在
transaction在更新資料的時候 可以只更新自己的版本
其他人也可以只讀自己的版本
例如 repeatable read
begin read(A) read(A) ... |
begin write(A) read(A) ... |
T1 T2
會 read 到自己剛剛修改的結果
兩次 read 結果會一樣
因為他看的是自己的版本
小總結
isolation level 爲 serializable 的時候
幾乎不會在 transaction 這部分
造成 application 資料的 inconsistency
但是因為效能代價太大 可能只有特殊情況才會用
大多資料庫管理系統預設的是 read committed
(MySQL 是 repeatable read)
不是用 serializable 的話
寫 application 的人就要注意更多可能造成 inconsistency 的狀況
必要的時候可以在 connection 改變 isolation level
=> set session transaction isolation level serializable
或是在某個 query 指定要拿 lock
=> SELECT ... FOR SHARE (shared lock)
=> SELECT ... FOR UPDATE (exclusive lock)
參考資料
Database Transactions
By luyunghsien
Database Transactions
- 511