本地事務的理論依據

放了方便描述,本問題討論的是都是page-oriented系統。道理都是一樣的,其他類型的系統也適用。在事務裏,有兩個最重要的特性:

  1. 原子性:原子性保證了事務的多個操作要麼都生效要麼都不生效,不會存在中間狀態
  2. 持久性:持久性保證了一旦事務生效,就不會再因爲任何原因而導致其修改的內容被撤銷或丟失

衆所周知,數據必須要成功寫入硬盤等持久化存儲器後才能擁有持久性。實現原子性和持久性的最大困難是“寫入硬盤”這個操作並不是原子的,不僅有“寫入”與“未寫入”狀態,還客觀地存在着“正在寫”的中間狀態。所以如果不做額外保障措施,只是簡單把內存中的數據寫入磁盤,並不能保證原子性與持久性。這個中間態,大致可以歸納成3種情形:

  1. 未提交事務,部分持久化,程序崩潰:例如修改5個數據,已經修改了3個,其中2個已經持久化。程序崩潰後重啓,因爲事務未提交,需要把持久化後的數據恢復回去。
  2. 已提交事務,部分持久化,程序崩潰:例如修改5個數據,已經修改完,返回調用方成功,數據未持久化。程序就崩潰後重啓,需要把丟失的變更重做回來。
  3. 已提交事務,完全沒持久化,程序崩潰:同2。

我們可以看到,這些問題可以歸納成2個問題:

  1. 把髒數據恢復成之前的數據
  2. 把丟失的數據恢復回來

爲了解決這兩個問題,有3個流派,我們每個流派都說一下

  1. Shadow Paging
  2. Commit Logging
  3. Write-Ahead Logging

Shadow Paging

基本思想是這樣的,對於問題1,如果我不in-place update,就沒有髒數據了,也就沒有把髒數據恢復之說; 對於問題2,如果事務提交成功的前提,數據已經持久化,那麼就不用丟失數據了。因此Shadow Paging在修改數據的時候,會先copy一份副本,基於這個副本做修改,如果需要修改多個數據,那麼就分別copy多個副本做修改,修改完後把數據持久化下來。事務提交時,就是把之前引用老數據的指針,指向新數據,然後持久化,持久化完後,事務就提交成功。如果被修改的指針分佈在幾個頁面上,那麼對每個一個頁面,也是執行shadow paging的策略去更新,是一個遞歸的過程。這裏大家可以看到有幾個問題:

  1. copy page本身的開銷,性能問題
  2. 修改引用page也可能走shadow paging,性能問題
  3. 提交事務需要數據頻繁持久化,性能問題(是否是隨機IO,取決於持久化層存儲引擎)
  4. 一些隔離級別做起來成本高,例如支持read commit,那麼就有mvcc,保存的是多個完整的page

可以看到shadow paging策略最大的問題,就是性能差。如果要優化的化,對於2、3,一般會結合接下來討論的兩個流派做優化。

Commit Logging

基本思想是這樣的,對於問題1,如果事務不提交,我就不持久化,那就沒髒數據了,也就沒有把髒數據恢復之說;對於問題2,事務提交成功的前提,是我把所有修改記錄,都append到日誌中,提交時把事務提交的標記也append到log中,然後做日誌持久化,這樣就完成的事務提交。這時恢復數據就很容易的,而且性能也高,因爲對於持久化存儲append的性能很高。因此使用Commit Logging策略的系統,每次修改數據前,都先append log,log並不要求刷盤,事務裏的所有數據都修改完了,append 一個commit標記到log中,然後對log進行刷盤,即事務完成提交了。對於事務提交後的部分持久化問題,可以通過在page處記錄持久化時對page做修改的日誌序號,避免重複做即可,或者把操作設計成保證冪等性。可以看到Commit Logging相比Shadow paging有不少優點:

  1. 不需要copy page,性能損耗小
  2. 提交事務,持久化的只是log,並且是append only,性能高
  3. mvcc好做,對於page內的每個記錄一個版本號即可,不需要額外保存完整的page

但是Commit Logging也有一個明顯的缺點,就是隻有事務提交後,數據才能做持久化。這樣在高併發常見下,可能不用充分利用硬盤的IO,而且對於大事務,所有數據都要在內存中hold住。爲了改善這兩點,就提出了Write-Ahead Logging。

Write-Ahead Logging

基本思想是這樣的,對於問題1,如果事務不提交,持久化page的前提,是我記錄下修改前的數據,那麼髒數據就能恢復了,這個log稱爲undo log;對於問題2,解決方法和Commit Logging一樣,這個log稱爲redo log。因此使用WAL的系統,修改數據的流程是這樣的,對於每個修改詩句,修改前先append一條undo log,再append一條redo log,然後修改數據,事務未提交前,有數據要持久化時需要保證對應的undo log已經持久化。所有數據修改完後,append 一個commit標記到log中,然後對log進行刷盤,即事務完成提交了。系統崩潰服務重啓,對數據進行恢復時,先不管三七二十一,把redo log裏所有明確有提交的事務重放一邊,然後在redo log裏所有沒提交的事務,去undo log那找log,把髒頁的數據回滾回去。可以看到和Commit Logging相比,優點是:

  1. 充分利用硬盤IO,釋放內存空間
  2. 大事務不需要把所有數據都hold在內存

理論化

我們將何時持久化變動數據,按照事務提交時點爲界,劃分爲 FORCE 和 STEAL 兩類情況:

  • FORCE:當事務提交時,要求變動數據必須同時完成寫入則稱爲 FORCE,如果不強制變動數據必須同時完成寫入則稱爲 NO-FORCE。
  • STEAL:在事務提交前,允許變動數據提前寫入則稱爲 STEAL,不允許則稱爲 NO-STEAL。


Shadow Paging FORCE + NO-STEAL。

Commit Logging NO-FORCE+ NO-STEAL。

Write-Ahead Logging NO-FORCE + STEAL。

現實中絕大多數數據庫採用的都是 NO-FORCE 策略,因爲只要有了日誌,變動數據隨時可以持久化,從優化磁盤 I/O 性能考慮,沒有必要強制數據寫入立即進行。從優化磁盤 I/O 性能考慮,允許數據提前寫入,有利於利用空閒 I/O 資源,也有利於節省數據庫緩存區的內存。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章