放了方便描述等曼,本問題討論的是都是page-oriented系統(tǒng)巡验。道理都是一樣的酱虎,其他類型的系統(tǒng)也適用下隧。在事務(wù)里魔吐,有兩個最重要的特性:
- 原子性:原子性保證了事務(wù)的多個操作要么都生效要么都不生效鹊碍,不會存在中間狀態(tài)
- 持久性:持久性保證了一旦事務(wù)生效舆床,就不會再因?yàn)槿魏卧蚨鴮?dǎo)致其修改的內(nèi)容被撤銷或丟失
眾所周知虑绵,數(shù)據(jù)必須要成功寫入硬盤等持久化存儲器后才能擁有持久性丧蘸。實(shí)現(xiàn)原子性和持久性的最大困難是“寫入硬盤”這個操作并不是原子的瑟枫,不僅有“寫入”與“未寫入”狀態(tài)春霍,還客觀地存在著“正在寫”的中間狀態(tài)砸西。所以如果不做額外保障措施,只是簡單把內(nèi)存中的數(shù)據(jù)寫入磁盤址儒,并不能保證原子性與持久性芹枷。這個中間態(tài),大致可以歸納成3種情形:
- 未提交事務(wù)莲趣,部分持久化杖狼,程序崩潰:例如修改5個數(shù)據(jù),已經(jīng)修改了3個妖爷,其中2個已經(jīng)持久化蝶涩。程序崩潰后重啟,因?yàn)槭聞?wù)未提交絮识,需要把持久化后的數(shù)據(jù)恢復(fù)回去绿聘。
- 已提交事務(wù),部分持久化次舌,程序崩潰:例如修改5個數(shù)據(jù)熄攘,已經(jīng)修改完,返回調(diào)用方成功彼念,數(shù)據(jù)未持久化挪圾。程序就崩潰后重啟,需要把丟失的變更重做回來逐沙。
- 已提交事務(wù)哲思,完全沒持久化,程序崩潰:同2吩案。
我們可以看到棚赔,這些問題可以歸納成2個問題:
- 把臟數(shù)據(jù)恢復(fù)成之前的數(shù)據(jù)
- 把丟失的數(shù)據(jù)恢復(fù)回來
為了解決這兩個問題,有3個流派,我們每個流派都說一下
- Shadow Paging
- Commit Logging
- Write-Ahead Logging
Shadow Paging
基本思想是這樣的靠益,對于問題1丧肴,如果我不in-place update,就沒有臟數(shù)據(jù)了胧后,也就沒有把臟數(shù)據(jù)恢復(fù)之說芋浮; 對于問題2,如果事務(wù)提交成功的前提壳快,數(shù)據(jù)已經(jīng)持久化纸巷,那么就不用丟失數(shù)據(jù)了。因此Shadow Paging在修改數(shù)據(jù)的時候濒憋,會先copy一份副本何暇,基于這個副本做修改,如果需要修改多個數(shù)據(jù)凛驮,那么就分別copy多個副本做修改裆站,修改完后把數(shù)據(jù)持久化下來。事務(wù)提交時黔夭,就是把之前引用老數(shù)據(jù)的指針宏胯,指向新數(shù)據(jù),然后持久化本姥,持久化完后肩袍,事務(wù)就提交成功。如果被修改的指針分布在幾個頁面上婚惫,那么對每個一個頁面氛赐,也是執(zhí)行shadow paging的策略去更新,是一個遞歸的過程先舷。這里大家可以看到有幾個問題:
- copy page本身的開銷艰管,性能問題
- 修改引用page也可能走shadow paging,性能問題
- 提交事務(wù)需要數(shù)據(jù)頻繁持久化蒋川,性能問題(是否是隨機(jī)IO牲芋,取決于持久化層存儲引擎)
- 一些隔離級別做起來成本高,例如支持read commit捺球,那么就有mvcc缸浦,保存的是多個完整的page
可以看到shadow paging策略最大的問題,就是性能差氮兵。如果要優(yōu)化的化裂逐,對于2、3胆剧,一般會結(jié)合接下來討論的兩個流派做優(yōu)化絮姆。
Commit Logging
基本思想是這樣的醉冤,對于問題1秩霍,如果事務(wù)不提交篙悯,我就不持久化,那就沒臟數(shù)據(jù)了铃绒,也就沒有把臟數(shù)據(jù)恢復(fù)之說鸽照;對于問題2,事務(wù)提交成功的前提颠悬,是我把所有修改記錄矮燎,都append到日志中,提交時把事務(wù)提交的標(biāo)記也append到log中赔癌,然后做日志持久化诞外,這樣就完成的事務(wù)提交。這時恢復(fù)數(shù)據(jù)就很容易的灾票,而且性能也高峡谊,因?yàn)閷τ诔志没鎯ppend的性能很高。因此使用Commit Logging策略的系統(tǒng)刊苍,每次修改數(shù)據(jù)前既们,都先append log,log并不要求刷盤正什,事務(wù)里的所有數(shù)據(jù)都修改完了啥纸,append 一個commit標(biāo)記到log中,然后對log進(jìn)行刷盤婴氮,即事務(wù)完成提交了斯棒。對于事務(wù)提交后的部分持久化問題,可以通過在page處記錄持久化時對page做修改的日志序號主经,避免重復(fù)做即可荣暮,或者把操作設(shè)計(jì)成保證冪等性≈嫉。可以看到Commit Logging相比Shadow paging有不少優(yōu)點(diǎn):
- 不需要copy page渠驼,性能損耗小
- 提交事務(wù),持久化的只是log鉴腻,并且是append only迷扇,性能高
- mvcc好做,對于page內(nèi)的每個記錄一個版本號即可爽哎,不需要額外保存完整的page
但是Commit Logging也有一個明顯的缺點(diǎn)蜓席,就是只有事務(wù)提交后,數(shù)據(jù)才能做持久化课锌。這樣在高并發(fā)常見下厨内,可能不用充分利用硬盤的IO祈秕,而且對于大事務(wù),所有數(shù)據(jù)都要在內(nèi)存中hold住雏胃。為了改善這兩點(diǎn)请毛,就提出了Write-Ahead Logging。
Write-Ahead Logging
基本思想是這樣的瞭亮,對于問題1方仿,如果事務(wù)不提交,持久化page的前提统翩,是我記錄下修改前的數(shù)據(jù)仙蚜,那么臟數(shù)據(jù)就能恢復(fù)了,這個log稱為undo log厂汗;對于問題2委粉,解決方法和Commit Logging一樣,這個log稱為redo log娶桦。因此使用WAL的系統(tǒng)贾节,修改數(shù)據(jù)的流程是這樣的,對于每個修改詩句趟紊,修改前先append一條undo log氮双,再append一條redo log,然后修改數(shù)據(jù)霎匈,事務(wù)未提交前戴差,有數(shù)據(jù)要持久化時需要保證對應(yīng)的undo log已經(jīng)持久化。所有數(shù)據(jù)修改完后铛嘱,append 一個commit標(biāo)記到log中暖释,然后對log進(jìn)行刷盤,即事務(wù)完成提交了墨吓。系統(tǒng)崩潰服務(wù)重啟球匕,對數(shù)據(jù)進(jìn)行恢復(fù)時,先不管三七二十一帖烘,把redo log里所有明確有提交的事務(wù)重放一邊亮曹,然后在redo log里所有沒提交的事務(wù),去undo log那找log秘症,把臟頁的數(shù)據(jù)回滾回去照卦。可以看到和Commit Logging相比乡摹,優(yōu)點(diǎn)是:
- 充分利用硬盤IO役耕,釋放內(nèi)存空間
- 大事務(wù)不需要把所有數(shù)據(jù)都hold在內(nèi)存
理論化
我們將何時持久化變動數(shù)據(jù),按照事務(wù)提交時點(diǎn)為界聪廉,劃分為 FORCE 和 STEAL 兩類情況:
- FORCE:當(dāng)事務(wù)提交時瞬痘,要求變動數(shù)據(jù)必須同時完成寫入則稱為 FORCE故慈,如果不強(qiáng)制變動數(shù)據(jù)必須同時完成寫入則稱為 NO-FORCE。
-
STEAL:在事務(wù)提交前框全,允許變動數(shù)據(jù)提前寫入則稱為 STEAL察绷,不允許則稱為 NO-STEAL。
Shadow Paging FORCE + NO-STEAL竣况。
Commit Logging NO-FORCE+ NO-STEAL克婶。
Write-Ahead Logging NO-FORCE + STEAL筒严。
現(xiàn)實(shí)中絕大多數(shù)數(shù)據(jù)庫采用的都是 NO-FORCE 策略丹泉,因?yàn)橹灰辛巳罩荆儎訑?shù)據(jù)隨時可以持久化鸭蛙,從優(yōu)化磁盤 I/O 性能考慮摹恨,沒有必要強(qiáng)制數(shù)據(jù)寫入立即進(jìn)行。從優(yōu)化磁盤 I/O 性能考慮娶视,允許數(shù)據(jù)提前寫入晒哄,有利于利用空閑 I/O 資源,也有利于節(jié)省數(shù)據(jù)庫緩存區(qū)的內(nèi)存肪获。