重新整理 mysql 基礎篇————— 介紹mysql日誌[二]

不問前世發表於2021-06-11

前言

對於後端開發來說,打交道最多的應該是資料庫了,因為你總得把東西存起來。

或是mongodb或者redis又或是mysql。然後你發現一個問題,就是他們都有日誌系統,那麼這些日誌用來幹什麼的呢?

舉兩個例子,回滾和同步。

回滾,這裡的回滾是比如說一條語句增加了1,然後再減一嗎?這裡的回滾操作並不是這樣的。

比如說我要更新一條語句,update test set a=1 where b=2,這樣的語句,如果這條語句需要回滾,那麼操作就應該是在執行前,先查詢這條資料進行儲存,如果執行完畢需要回滾,那麼就直接把原來那條語句寫回去。

又比如說,你的資料庫要還原到一個小時前,那麼你可以把2個小時前的備份拿出來,然後執行前兩個小時到前一個小時的日誌檔案,那麼這個時候就相當於回到了一個小時前了。

同步,比如說主從同步了,這樣老生常談的了,一般通過事務日誌來同步。

總之,有了日誌,那麼可以幫我們實現很多功能的了。

那麼mysql 在innodb 引擎下,有兩個日誌非常重要,那就是redo log(重做日誌)和 binlog(歸檔日誌)日誌。

如果沒有這兩個日誌,應該沒啥人敢用mysql的了,因為這兩個日誌使用了保證mysql的資料完整性的,如果一個資料庫連完整性都不能保證,那麼是非常危險的。

正文

redo log

首先看下redo log,這個是什麼呢? 這個是innodb儲存引擎的日誌。

說一個它的功能哈,前文提及到儲存引擎就相當於我們作業系統的檔案系統。

那麼問題來了,我們的檔案系統是有快取的,比方說我們寫入一個檔案,當我們呼叫函式的時候,不會直接寫入,而是寫入快取去,而後又檔案系統自己判斷啥時候應該寫入進去。

判斷啥時候應該寫入進去,其中有一個標準就是這次要寫入快取的時候,判斷快取是否能夠裝的下,如果裝不下,那麼先寫入檔案,清除快取,然後再寫入快取。

第二個判斷標準就是根據時間某一段時間後進行寫入。

同樣儲存引擎也要為這一段事情操心啊。如果我們更新一條語句,儲存引擎就直接給我們操作正在儲存資料的地方,那效率可想而知。

所以說,儲存引擎就想到一個方法,把更新記錄記錄到redo log 中,等redo log 快寫滿,然後就操作到磁碟,或等空閒時間更新進去。

寫完redo log 之後,就會告訴執行器,執行完畢了。這個時候我們的應用程式得到更新成功的回撥。

如果單純只寫入redo log是不行的,因為儲存過程不僅要寫,還要讀啊,如果寫完redo log 通知我們的應用程式更新成功,這個時候還沒寫入到資料檔案,那麼我們的應用程式去讀的時候就讀到了舊的資料。

那麼這個時候,儲存引擎是這麼幹的。 反正你給我查詢的時候要先查詢記憶體,記憶體中沒有才去查詢資料檔案。那麼儲存引擎,先更新到記憶體中,然後更新到redo log。這樣對於儲存引擎外部來說,是更新了的,畢竟對於外部來說儲存引擎是一個整體。

這就是MySQL裡經常說到的WAL技術,WAL的全稱是Write-Ahead Logging。

那麼這個redo log 是一個什麼樣的機制呢?難道就一直記錄到一個檔案中,然後當要寫入資料檔案的時候,全部寫入,然後刪除?

如果這麼幹效率自然是低了。redo log的機制是這樣的。

redo log 是由四個檔案組成,每個檔案大小為1G左右,這個都是可以設定的。

有兩個引數,分別為checkpoint 和 write pos。

write pos 是當前記錄的位置。checkpoint 是當前寫入到資料檔案的位置。

比如說:

一開始的時候write pos 寫的了第二個檔案的問題,如果為位置1000。

這個時候還沒有去正在寫入資料檔案,那麼這個時候checkpoint 位置就是0。要往資料檔案中寫入資料的部分就是checkpoint 到 write pos這一段區間,也就是0-1000位置。

那麼這個時候儲存引擎感覺可以更新了,然後開始寫入到正在的資料檔案中,那麼這個時候開始checkpoints 開始往右移動,假設更新800條。

那麼就到了下面這個位置:

這個時候儲存引擎感覺比較忙了,那麼就更新800條後,繼續接執行器的任務,那麼write pos 往右繼續移動。

那麼這個時候就有一個問題出現,比如說檔案大小不變,write pos 一直往右移動,這樣會超出啊。

那麼這個時候write pos 發現自己到了末尾,人家又從第一個開始寫,覆蓋寫入。

如下:

綠色橫線部分是要更新到資料檔案部分。

redo log 還有一個重要的作用,保證資料正在的寫入到資料檔案中。

比如說這個時候正在寫入資料檔案,然後資料庫異常重啟了,這裡理解異常重啟,簡單點理解就是記憶體都沒了。

那麼我們知道寫入檔案是有快取的,如果寫入到一半異常了,那麼資料其實是丟了的。

有了redo log之後,只有資料檔案flush了,那麼這個時候checkpoint才開始偏移,否則就如果異常了記憶體沒了,那麼繼續覆蓋更新,因為checkpoint 沒有變化,那麼還是從原來那個異常前的位置開始同步。

那麼問題來了,這時候就會想,資料檔案是檔案,redo log也是檔案啊。如果寫入的時候在快取區,然後當機這個時候也沒了啊。

沒錯,的確有這個問題,這個時候為了資料安全,redo log直接不使用快取區。

redo log用於保證crash-safe能力。innodb_flush_log_at_trx_commit這個引數設定成1的時候,表示每次事務的redo log都直接持久化到磁碟。

binlog

binlog 是每個mysql都有的,而不是儲存引擎的東西,屬於mysql 的server 層的東西。

對比一下binlog 和 redo log。

這兩種日誌有以下三點不同。

redo log是InnoDB引擎特有的;binlog是MySQL的Server層實現的,所有引擎都可以使用。

redo log是物理日誌,記錄的是“在某個資料頁上做了什麼修改”;binlog是邏輯日誌,記錄的是這個語句的原始邏輯,比如“給ID=2這一行的c欄位加1 ”。

redo log是迴圈寫的,空間固定會用完;binlog是可以追加寫入的。“追加寫”是指binlog檔案寫到一定大小後會切換到下一個,並不會覆蓋以前的日誌。

一條更新語句在innodb引擎下的更新過程:

1. 執行器先找引擎取ID=2這一行。ID是主鍵,引擎直接用樹搜尋找到這一行。如果ID=2這一行所在的資料頁本來就在記憶體中,就直接返回給執行器;否則,需要先從磁碟讀入記憶體,然後再返回。

2. 執行器拿到引擎給的行資料,把這個值加上1,比如原來是N,現在就是N+1,得到新的一行資料,再呼叫引擎介面寫入這行新資料。

3. 引擎將這行新資料更新到記憶體中,同時將這個更新操作記錄到redo log裡面,此時redo log處於prepare狀態。然後告知執行器執行完成了,隨時可以提交事務。

4. 執行器生成這個操作的binlog,並把binlog寫入磁碟。

5. 執行器呼叫引擎的提交事務介面,引擎把剛剛寫入的redo log改成提交(commit)狀態,更新完成。

這個時候有人可能會問,如果第3步驟先更新到記憶體,這個時候要是讀取操作而之後redo log沒有寫入就當機怎麼辦?因為是寫入是有鎖的,如果沒有提交事務,這個時候有寫鎖。

這時候就發現一個細節,那就是redo log 一條記錄有兩個狀態一個是prepare,一個是commit狀態。

那麼為什麼要兩個狀態呢?

我們知道mysql 主從,其實是通過binlog 一條一條傳送給從資料庫,讓從資料庫執行binlog裡面的操作。

假設沒有這兩個狀態。

假如innodb 寫入redo log之後呢,這個時候資料庫突然當機了,這個時候redo log 是有的記錄的,這個時候binlog 沒有記錄。

那麼innodb 通過redo log 進行寫入到資料檔案後,binlog 依然沒有這一條記錄。那麼從庫就少了一條操作了。

這個時候主從永遠不可能一致。

如果有了兩個狀態,資料庫重啟後,innodb儲存引擎還是會通知binlog。這時候兩個狀態就保證了binlog裡面的資料完整性。

那麼這個時候又會問了,假如上面第四步執行了,第五步沒有執行怎麼辦?比如當機了。

是啊,這個時候bin log 中有記錄但是redo log沒有記錄。

那麼從庫就少了一條操作記錄了。

這個時候主從永遠不可能一致。同樣,我們如果資料庫退回到某個時間點,如果binlog 和 redolog不一致的話,同樣適用binlog進行回滾一樣的會遇到這個問題。

如果有了redo log 的prepare 狀態,那麼如果資料庫重啟的時候檢測到當機,這個時候redo log裡面prepare 狀態的資料就會和binlog裡面的資料進行校驗,進而進行恢復。

這種有兩個狀態的提交,叫做兩階段提交。他們起到的作用是如果當機檢測到異常,就會對比恢復。

同樣binlog也是檔案,同樣存在快取的問題,sync_binlog這個引數設定成1的時候,表示每次事務的binlog都持久化到磁碟。

以上只是個人整理,如有錯誤,望請指點。

下一節事務。

相關文章