本文為 Design Data Intensive Applications 的書摘 + 個人心得。
單物件 (Single-Object) 和多物件 (Multi-Object) 操作
多物件 (Multi-Object) 操作
ACID 的原子性和隔離性能讓使用端在一個 transaction 下有多次寫入,並能一次操作多個物件 (Object)。
物件可以指一筆資料、一個文件 (document) 或一筆記錄 (record)。
如下圖 7-3 的多物件修改那樣,多物件修改通常需要資料保持同步,User 1 替收件 ID 2 新增了一筆 email 資料,則 ID 2 的信箱未讀數量就需要 +1;但 User 2 的體驗就很奇怪了,明明 ID 2 的 email 是未讀的,但信箱的未讀數卻是 0 !?
此時 隔離性 就能避免這種情形了,隔離性能保證 transaction 不會互相影響,所以 User 2 要嘛只會讀更新前或後的資料,不會有那種不一致的一半資料。
下圖 7-3 則是展示了另外一種多物件操作時會發生的情況,不幸的更新信箱時爆炸了,在 原子性 的保證下,整個 transaction 會中斷,已新增的 email 會 rollback 。
多物件操作需要某種方法檢測讀取跟寫入是屬於同一個 transaction,在 RDB 中,一個典型的方法就是在同一個 TCP 連線下,任何 BEGIN TRANSACTION
和 COMMIT
之間都視為同一個 transaction。
這不是個很理想的方法喔,因為 TCP 有斷線的可能,如果斷在 commit 送出後使用者未收到結果時,工程師會不知道到底成功 commit 了沒有,解法是給 transaction 一個識別 ID,使該 transaction ID 不綁定在特定的 TCP 連線上,這個主題應該會在 Day 28~29 時聊到。
單物件 (Single-Object) 操作
原子性和隔離性也能運用在單物件操作上,舉例來說,假設你正在資料庫更新一個 20 KB 的 JSON document (value = JSON):
- 如果網路在你傳了 10 KB 後中斷,你要處理這 10 KB 片段的 JSON 嗎?
- 資料寫硬碟寫到一半斷電了,你能在結束寫入時拼接舊資料和新資料嗎?
- 在你寫入資料時另一個使用者讀取該 document,他能看到部份已更新的資料嗎?
這些狀況如同多物件操作的問題那樣令人困惑,所以大多數的儲存引擎在單一節點上的單物件操作都會支援原子性和隔離性 (包含 key-value 類型資料),原子性可使用 log 來恢復資料(B-Tree 的可靠性),而隔離性就可使用鎖 (lock) ,資料更新後就倚靠之前講過的 replication 機制去同步資料。
Transaction 是必要的嗎?
有些 No-SQL 愛好者或許會認為:資料庫在單物件操件上有支援 ACID,加上它們避免 更新遺失 (lost update) 的方法是使用 比較並交換 (Compare-and-set),這樣就像個輕量化的 transaction 了!
PS 1: 之後會聊聊有哪些避免 更新遺失 (lost update) 的方法。
PS 2: 該書作者認為,transaction 就是一個機制能把多物件操作視為一個執行,所以希望大家不要在用奇怪的術語誤導大家了XD
但是在現實世界中,單物件操作上還是會遇到需要 transaction 的場景,例如在 document 資料模型上,儘管你做了 denormalized ,還是有可能會有像上圖 7-2 那樣,你需要同時更新 email 和 mailbox 等不同 document 資料的時候,有 transaction 是不是就能讓工程師少煩惱一點了呢?
有一好沒二好啦,有很多分佈式儲存引擎棄用了多物件操作 transaction(很難在跨分區上實現),但它給了我們強大的高可用性和高性能,那這樣分佈式 transaction 就沒希望了嗎?我們應該會在 x 日之後 談如何實現分佈式 transaction。