概述
這段時(shí)間對(duì)mysql數(shù)據(jù)庫(kù)事務(wù)的acid做了一些深入的研究囱皿,理解了如何在異常情況下保證事務(wù)的acid氏堤。而最嚴(yán)重的異常情況莫過于斷電必盖,隨時(shí)都有可能發(fā)生且發(fā)生時(shí)不會(huì)有任何保存的機(jī)會(huì)赞咙,所以就想以事務(wù)處理的各個(gè)階段發(fā)生斷電的情況下mysql如何保證acid為切入點(diǎn)總結(jié)一下這段時(shí)間所學(xué)到的東西普监。
本文將重點(diǎn)介紹commit命令執(zhí)行的各個(gè)階段贵试,mysql是如何進(jìn)行斷電恢復(fù)的。
mysql的page凯正、日志和buffer介紹
page
mysql的page與文件系統(tǒng)的block類似毙玻,是mysql自己定義的讀取和寫入磁盤的最小單位,其目的是為了充分的利用磁盤的順序訪問速度快的優(yōu)勢(shì)廊散。一般是磁盤扇區(qū)的4~16倍桑滩。
logs
- undolog。即 mvcc 歷史快照允睹,記錄的是某行過去的某個(gè)版本运准,用于幫助事務(wù)回滾及 MVCC 的功能。存儲(chǔ)在表空間缭受,以 transaction 為維度記錄胁澳,且記錄了 transaction 狀態(tài) ,可用于 crash 恢復(fù)時(shí)提取未提交的事務(wù)米者【禄可以理解成一張?zhí)厥獾谋恚矔?huì)用到 buffer pool 蔓搞。
- redolog胰丁。可以理解為內(nèi)存數(shù)據(jù)的WAL(write-ahead-log)喂分,用來恢復(fù)未flush到disk的page數(shù)據(jù)锦庸。記錄的是對(duì)頁的二進(jìn)制級(jí)別的修改操作,比如某個(gè)偏移寫入'aa'記錄蒲祈。** 一般存儲(chǔ)在單獨(dú)的redo log文件中甘萧,數(shù)據(jù)庫(kù)全局的。另外要注意的是讳嘱, undolog 的寫入也會(huì)生成對(duì)應(yīng)的 redo log 幔嗦。**
- binlog。即我們常說的二進(jìn)制日志沥潭,主要用于主從復(fù)制。
buffer
- buffer pool嬉挡。即 mysql page 的 buffer 钝鸽。使用 lru 來管理汇恤。
- doublewrite buffer。在 flush page 到磁盤之前拔恰,會(huì)先將 page 寫入這里因谎,為了防止 page flush 到一半時(shí)斷電造成的磁盤 page 數(shù)據(jù)不完整的問題。存儲(chǔ)介質(zhì)雖為文件颜懊,但充分利用順序?qū)懖撇恚院芸臁?/li>
- log buffer。即 redolog 的內(nèi)存 buffer 河爹。 用于降低 redolog 對(duì)于寫入速度的影響匠璧。
除了這些還有 change buffer 等,不影響接下來的內(nèi)容介紹咸这,故不在此處展開討論夷恍。
mysql寫入過程簡(jiǎn)介
commit 之前
先說說 commit 命令之前,寫入操作對(duì)應(yīng)的過程媳维。 此時(shí)所有的寫入操作都會(huì)通過 undolog 的支持酿雪,同時(shí)保留寫入之前的版本和寫入之后的版本,而這里對(duì)表數(shù)據(jù)和 undolog 的修改并不會(huì)直接寫入到磁盤中侄刽,而是寫入 buffer pool 中指黎,同時(shí)生成相應(yīng)的 redolog 。而此時(shí)的 redolog 存放在 log buffer 中州丹,這樣做的目的是為了減少 redolog 對(duì)寫入性能的影響袋励。想象一下,如果每次寫入產(chǎn)生的 redolog 都寫入磁盤当叭,即使是順序?qū)懭氩绻剩矔?huì)造成不小的影響。所以蚁鳖,如果一個(gè) transaction 中包含大量的寫入操作磺芭,最好將 log buffer 調(diào)大一些,避免不必要的磁盤 io 醉箕。
下面說說 commit 的執(zhí)行過程钾腺。由于在生產(chǎn)環(huán)境中,絕大多數(shù) mysql 實(shí)例都會(huì)開啟 binlog 讥裤,因此我們主要介紹開啟 binlog 時(shí) commit 的實(shí)行過程放棒。
commit 執(zhí)行過程
在開啟 binlog 的情況下, mysql 使用 2PC 來執(zhí)行 commit 過程己英,具體步驟如下:
- 階段1:將 undolog 中的 transaction 狀態(tài)改為 prepared 间螟,并生成相應(yīng)的生成 redolog ,然后 flush log buffer 。
- 階段2:將 transaction 寫入 binlog 厢破,然后告訴底層存儲(chǔ)引擎將 undolog 中的 transaction 狀態(tài)改為 commited 并生成 redo log 荣瑟。只要寫入binlog成功,就可以認(rèn)為事務(wù)寫入成功摩泪。
另外需要注意的是笆焰,此時(shí) mysql 使用 xid 來標(biāo)識(shí)該事務(wù)。
斷電恢復(fù)過程
完成了 prepare 階段见坑,寫入 binlog 之前斷電
這種情況下嚷掠, mysql 重新啟動(dòng)時(shí),會(huì)從 redolog 中讀出未 flush 到磁盤中的 page —— buffer pool 荞驴。然后從 redolog 重建這些內(nèi)存中的 page 不皆,以恢復(fù)斷電之前內(nèi)存的狀態(tài)。之后戴尸,mysql檢測(cè)到該事務(wù)并未提交粟焊,因此主動(dòng)執(zhí)行事務(wù)的回滾操作。
flush buffer pool 時(shí)斷電
即 flush page 的時(shí)候孙蒙。此時(shí)造成 page 一部分?jǐn)?shù)據(jù)寫入磁盤项棠,另一部分?jǐn)?shù)據(jù)未寫入磁盤。這時(shí)無法從 redo log 恢復(fù)異常 page 挎峦。原因是:之前提到 redo log 保存的page的修改記錄香追,即從原 page 到新 page 的 patch ,而此時(shí)原 page 已經(jīng)丟失(因?yàn)?page 的一部分?jǐn)?shù)據(jù)已經(jīng)寫入磁盤)坦胶,所以無法從 redo log 恢復(fù)透典。這時(shí)就要用到 double write buffer 了。每次 flush page 之前顿苇,都會(huì)先將 page flush 到一個(gè) double write buffer 峭咒,如果在真正 flush page 的時(shí)候出現(xiàn)異常,則可以從 double write buffer 恢復(fù)這個(gè) page 纪岁。
flush log buffer 時(shí)斷電
即 redolog 的flush凑队。由于 redolog 的 flush 是以扇區(qū)為單位進(jìn)行 flush 的,而扇區(qū)的寫入是原子性的(當(dāng)代的大部分硬盤都有這個(gè)能力)幔翰,因此不會(huì)造成部分寫入员帮、部分未寫入的情況周拐。
寫完 binlog 后斷電
上面說到睛约,寫入binlog時(shí)帝洪,便可認(rèn)為事務(wù)提交成功,就可以保證acid中的d做修。此時(shí)如果斷電霍狰,mysql在啟動(dòng)會(huì)執(zhí)行如下過程:
- 先從binlog中篩選出所有xid并創(chuàng)建hash_list
- 然后從存儲(chǔ)引擎(如innodb)中選出prepared狀態(tài)的transaction的xid列表list抡草。一般是從rollback segments(rseg) —— undo log ——中提取xid列表,然后在選出prepared狀態(tài)的蚓耽。
- 遍歷list的xid渠牲,如果xid在hash_list中則commit旋炒,否則rollback步悠。
寫入 binlog 時(shí)斷電
binlog 的寫入也是沒有 double write buffer 保護(hù)的,那么在寫入 binlog 的過程中如果發(fā)生了斷電瘫镇, mysql 是如何恢復(fù)的呢鼎兽。如上面介紹的寫入 binlog 后斷電恢復(fù)過程所示, mysql 會(huì)從 binlog 中選出所有的 xid 并創(chuàng)建恢復(fù)列表铣除,不過在遍歷 binlog 的過程中谚咬, mysql 同時(shí)也會(huì)尋找 last valid position ,并將這個(gè) position 之后的內(nèi)容都截取掉尚粘。因此择卦,如果某個(gè) transaction 寫入 binlog 的過程中斷電,并且有一部分未成功寫入郎嫁,則該 transaction 在 mysql 重啟時(shí)會(huì)被回滾秉继。
相關(guān)配置
這些配置是mysql5.7之后的默認(rèn)配置:
- sync_binlog=1
- innodb_support_xa=ON
- innodb_flush_log_at_trx_commit=1。用于控制 log buffer 的刷新時(shí)機(jī)泽铛。
參考資料
《Mysql技術(shù)內(nèi)幕》
https://dev.mysql.com/doc/refman/5.7/en/glossary.html
mysql5.7源碼——sql/binlog.cc
作者: Normal Cock
鏈接: https://normal-cock.github.io/passages/mysql-power-off-and-recovery/
來源: normal-cock.github.io
著作權(quán)歸作者所有尚辑。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處盔腔。