憑什么讓日志先寫?
在生活中,你一定有過類似這樣的經(jīng)歷:
比如部門發(fā)禮品、或者說學(xué)校發(fā)課本。如果在發(fā)放的時候,大家一窩蜂的涌了過來,畢竟雙拳雙敵四手,漸漸你就招架不過來。
為了工作更好做,你會有幾個選擇,提前打印個名單,一個個來領(lǐng),領(lǐng)的人在名單上打勾,東西拿走?;蛘叽蠹叶紒砟?,你看一眼,記在腦海里,但可能中途打個岔就記錯了。也可以記住是誰,找個紙記下來,每次記一下或者隔一會記幾下。
對比上面的場景,你沒有發(fā)現(xiàn),不同的方式,效率上也存在的差別。比如在名單上找到打勾,那就會完全串行,每個人來都得在定位到自己那條信息上,找的過程費時間。
找一張紙,每隔一段時間記一次,這樣效率也還挺高,問題就在于別人打岔的頻率。
在計算機科學(xué)領(lǐng)域也存在這樣的時候,比如我們常用的數(shù)據(jù)庫系統(tǒng)里。數(shù)據(jù)庫為了不喝娃哈哈AD鈣奶,就能保證ACID中的A和D,使用了一種被稱做「WAL」的機制。全稱是write-ahead logging。數(shù)據(jù)庫中所有的變更,會先寫到日志里,最后才會寫到持久存儲的數(shù)據(jù)文件中。像MySQL 里的 redo log 和undo log 就是這種機制。
像你在紙上記錄一樣,一直不停的向后寫,順序?qū)?,速度就會快,時不時的回過頭去檢查一下,改一下速度就降下來了。
如果大量記錄到白紙上的內(nèi)容,沒有及時的匯總記錄到一個表格上,那等最后全部匯總也比較費力氣。就像數(shù)據(jù)庫里一直寫WAL之后就應(yīng)用到內(nèi)存數(shù)據(jù)修改,速度很快,但如果出現(xiàn)故障的時候,就需要重新回放大量的redo log,恢復(fù)時間也無法接受。
就像行政急著要結(jié)果的時候,你才開始「回放」白紙上的內(nèi)容,就會慢很多。如果是在發(fā)放的過程中,可以過一段時間匯總一下,然后在白紙上加個「標記」,用于一會提示自己上次算到什么位置了。這種就是數(shù)據(jù)庫里的 checkpoint,下次恢復(fù)的時候,就直接從checkpoint 開始向后恢復(fù)就行,前面的已經(jīng)持久化到了磁盤,不用再費事了。
此外,計算機里,許多時候,都是一個根據(jù)自己的場景權(quán)衡的過程。比如對于使用WAL的時候,MySQL 提供了不同的配置來支持什么時機,多長時間將 log 應(yīng)用到數(shù)據(jù)文件,畢竟log 寫到磁盤也還是要花點兒時間的。每次都刷盤,會影響效率,但間隔時間太長,就會在機器故障的時候丟失數(shù)據(jù)。
MySQL 默認將log 刷到磁盤的時機有三個:
- 提交一個事務(wù)的時候
- 固定大小的 log buffer 滿了的時候
- 無論 log buffer 是否滿,每秒會刷一次
redo log 寫磁盤的過程
redo log buffer和page cache都是在內(nèi)存中,所以寫入這兩者都比較快,而fsync則需要消耗磁盤IO。Mysql的后臺每隔1秒也會自動將redo log buffer中的內(nèi)容刷到磁盤中去。
借用MySQL 官方博客的幾張圖來說明下
那有了redo log,就保證了故障時安全了嗎?是的。
機器在故障的時候,內(nèi)存中包含數(shù)據(jù)的內(nèi)容,也就是所謂的「臟頁」一般就會丟了,怎么樣恢復(fù)丟失的數(shù)據(jù)呢?咱們前面看到,redo log 先刷盤,之后真正的數(shù)據(jù)庫變更才刷盤,所以我們丟失的數(shù)據(jù)已經(jīng)保存在磁盤中的redo log里了。重放redolog 就可以。但這里有例外的情況是MySQL 里包含一個 innodb_flush_log_at_trx_commit 的配置,默認是1,即嚴格的D,非1的情況下會丟失redo log buffer和page cache中的數(shù)據(jù)。
本文轉(zhuǎn)載自微信公眾號「 Tomcat那些事兒」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系 Tomcat那些事兒公眾號。