幾年了?作為一個碼農終於把MySQL日記看懂了,為此肝出此文!!!

2020-10-12 11:00:24

目錄

一、寫作背景

二、文章指引

三、必要概念字典介紹

四、認識二進位制日記(Binlog)

4.1 Binlog概念

4.2 Binlog 作用

4.3 Binlog 記錄過程及刷盤時機

4.4 Binlog 記錄格式

五、認識事務日記(Undo log)

5.1 Undo log 概念

5.2 Undo log 作用

5.3 Undo log 記錄過程及刷盤時機

5.4 Undo log 總結

六、認識事務日記 (Redo log)

6.1 Redo log 概念

6.2 Redo log 作用

6.3 Redo log 的兩階段提交

6.4 Redo log 容災恢復過程

6.5 Redo log 刷盤時機

6.6 Redo log 儲存方式

6.7、Redo Log 檢查點

6.8 Redo Log LSN

6.9 Redo log 容災恢復過程與LSN

七、瞭解 ChangeBuffer

7.1 為啥提到ChangeBuffer

7.2 ChangeBuffer概念及作用

7.3 ChangeBuffer與Redo log區別

7.4 有沒有用到ChangeBuffer對於Redo log的區別

7.5 ChangeBuffer的merge過程

八、日記大連貫U-R-B,一舉攻破拿下

8.1 製造演示資料

8.2 假設沒有日記和ChangeBuffer 示範

8.3 考慮所有日記和ChangeBuffer 示範--現有Innodb流程

8.3.1 兩階段提交過程

8.3.2 merge 過程

8.3.3 資料刷盤過程

九、結尾


一、寫作背景

大家都清楚,紀錄檔是 MySQL資料庫的重要組成部分,記錄著資料庫執行期間各種狀態資訊。MySQL紀錄檔主要包括錯誤紀錄檔查詢紀錄檔慢查詢紀錄檔二進位制紀錄檔(binlog)事務紀錄檔(redo log、undo log)幾大類。

其中,二進位制日記事務日記尤為重要,一直被人重視、深入研究;可是事實很殘忍,重視或者說大多數人一般都是瞭解個表面,真正懂得人並不多。真想攻破這兩塊日記必須下血本,而且還不一定能攻破。但是不要緊,為了讓你們省下血本還能順利攻破這兩塊日記,我連續研究幾周MySQL日記,最終肝出了這篇文章

 

二、文章指引

文章指導:文章第三節內容切莫跳過但如果覺得第四、第五、第六和第七節沒意思或者已經有了概念直接進入文章第八節一舉攻破拿下。

文章方向:理論、原理篇。

 

三、必要概念字典介紹

基礎不牢地動山搖,還是常規套路,先把必要知識普及/溫習一遍,當後續文章出現疑慮反過來看下這些概念字典,說不定能 「柳暗花明又一村」 呢?

寫了又寫,想了又想,糾結了好久,這部分知識確實有點多,最後還是決定將這些必要概念字典單獨分出一個文章,後續打算用截圖方式引入各個章節中,建議遇到不懂名詞查閱一下字典。

點選獲取通往字典的大門的鑰匙--想學習進階資料庫知識???這些概念必須攻破!

圖1:進階知識部分示意圖

 

四、認識二進位制日記(Binlog)

4.1 Binlog概念

Binlog 是邏輯日記,用於記錄資料庫執行的寫入操作(查詢不記錄)資訊,Server層記錄和引擎層無關,並且是以追加方式進行寫入,可以通過引數 max_binlog_size 設定每個Binlog檔案的大小,檔案大小達到設定值時會生成新的檔案來儲存日記。

      

 

4.2 Binlog 作用

在實際應用中,主要用在兩個場景:主從複製和資料恢復

  • 主從複製場景:在Master主端開啟Binlog,將Binlog發生到各個Slave從端,Slave從端重放Binlog從而達到主從資料一致
  • 資料恢復場景:通過使用 mysqlbinlog 工具來恢復資料

 

4.3 Binlog 記錄過程及刷盤時機

Binlog何時記錄將在第六點進行介紹,大致記錄過程是先寫Binlog Buffer,然後通過刷盤時機,控制刷入OS Buffer,控制fsync()進行寫入Binlog File日記磁碟的過程。

對於Binlog,MySQL是通過引數sync_binlog引數來控制刷盤時機,取值是0、1和N三種值。0表示由系統自行判斷何時呼叫sync()寫入磁碟;1表示每次事務commit都要呼叫fsync()寫入磁碟;N表示每N個事務,才會呼叫fsync()寫入磁碟。

                                                

圖2:記憶體和磁碟日記結構圖

 

4.4 Binlog 記錄格式

MySQL5.7.7版本之前預設格式是STATEMENT,版本之後預設是ROW,可以通過引數 binlog-format指定。

STATMENT

基於SQL語句複製,每一條修改資料的sql語句都會被記錄到Binlog中

優點:不需要記錄每一行的變化,記錄Binlog日記量減少。

缺點:在某種情況下會導致主從資料不一致,比如執行sysdate()、slepp()等,具體可以自己去深入瞭解。

ROW

推薦使用,MySQL高版本的預設值。基於行的複製,不記錄每條sql資訊,僅僅記錄哪行資料被修改了

優點:沒有STATMENT模式中所說的主從資料不一致情況。

缺點:記錄行,需要更多的空間,會產生大量的日記。

MIXED上面兩種模式的混合。一般情況採用STATMENT模式儲存Binlog,特殊情況採用ROW模式儲存Binlog。

 

五、認識事務日記(Undo log)

5.1 Undo log 概念

Undo log是邏輯日記、回滾日記。比如一條修改+3的邏輯語句,Undo log會記錄對應一條-3的邏輯日記,一條插入語句則會記錄一條刪除語句,這樣發生錯誤時,根據執行Undo log就可以回滾到事務之前的資料狀態。

 

5.2 Undo log 作用

  • 回滾資料:當程式發生異常錯誤時等,根據執行Undo log就可以回滾到事務之前的資料狀態,保證原子性,要麼成功要麼失敗。
  • MVCC一致性檢視:通過Undo log找到對應的資料版本號,是保證MVCC檢視的一致性的必要條件。

 

5.3 Undo log 記錄過程及刷盤時機

刷盤過程及時機類似於Binlog和Redo,可以參考Redo log刷盤時機章節給出的圖片,已經體現出來了。

 

5.4 Undo log 總結

Undo log日記內容不是很多,重點是回滾多版本控制MVCC那塊。此外,我記得印象筆記深刻的是長事務會導致日記過多,這個日記就是Undo log。因為長事務存在,導致需要儲存很多檢視快照,其實這裡就是設及到Undo log何時刪除和生成的問題,當時糾結好久,其實很簡單。生成是事務開始後寫Redo log之前生成,當沒有事務需要用到Undo log時就會被刪除。舉個例子,如果事務A一直存活,那麼事務A之後產生的事務B、C...等等就算提交了,也不會被刪除,因為事務A需要用到B、C...事務去找A的版本。所以避免長事務可以減少Undo log日記量,當然還可以提高效能。

 

六、認識事務日記 (Redo log)

6.1 Redo log 概念

Redo log 是重做日記,屬於InnoDB引擎的日記。是物理日記,日記記錄的內容的是資料頁的更改,這個頁 「做了什麼改動」。如:add xx記錄 to Page1,向資料頁Page1增加一個記錄。

 

6.2 Redo log 作用

  • 前滾操作:具備crash-safe能力,提供斷電重新啟動時解決事務丟失資料問題。
  • 提高效能:先寫Redo log記錄更新。當等到有空閒執行緒、記憶體不足、Redo log滿了時 「刷髒」。寫Redo log是順序寫入,刷髒是隨機寫,節省的是隨機寫磁碟的 IO 消耗(轉成順序寫),所以效能得到提升。此技術稱為WAL技術:Write-Ahead Logging,它的關鍵點就是先寫日記磁碟,再寫資料磁碟

 

6.3 Redo log 的兩階段提交

更新記憶體後引擎層寫Redo log將狀態改成prepare為預提交第一階段,Server層寫Binlog,將狀態改成commit為提交第二階段。兩階段提交可以確保Binlog和Redo log資料一致性。

 

6.4 Redo log 容災恢復過程

MySQL的處理過程如下

  • 判斷redo log是否完整,如果判斷是完整(commit)的,直接用Redo log恢復
  • 如果redo log只是預提交prepare但不是commit狀態,這個時候就會去判斷binlog是否完整,如果完整就提交Redo log,用Redo log恢復,不完整就回滾事務,丟棄資料。

只有在redo log狀態為prepare時,才會去檢查binlog是否存在,否則只校驗redo log是否是 commit就可以啦。 怎麼檢查binlog:一個完整事物binlog結尾有固定的格式。

 

6.5 Redo log 刷盤時機

Undo log的刷盤時機和Redo log差不多,但是對於Undo log我沒找到對應的刷盤引數設計,所以不在提。Redo log每次先寫入Redo Log Buffer中,然後通過刷盤時機控制刷入OS Buffer時間和刷入日記磁碟的時間。

圖3:記憶體和磁碟日記結構圖

 

在Undo Log中,MySQL是通過引數innodb_flush_log_at_trx_commit來控制刷盤時機,取值是0、1和2三種值。0表示事務提交後,每秒寫入OS Buffer並呼叫fsync()寫入日記磁碟中;1表示每次事務提交會寫入OS Buffer並呼叫fsync()將日記寫入日記磁碟中。2表示事務每次提交寫入到OS Buffer,每秒呼叫fsync()寫入日記磁碟。可見引數為1是最安全的,同時也是預設值。

圖4:Redo log刷盤時機引數對應操作圖

 

6.6 Redo log 儲存方式

圖5:Redo log File環形儲存結構圖

上圖是日記磁碟的Redo log環形設計圖(從頭寫,寫到結束又從頭開始寫~迴圈)。write pos和check point是兩個指標,write pos指標指向當前日記檔案寫入的位置check point指標指向當前要擦除的開始位置。圖中綠色部分是可以寫入Redo log地方,每次寫入,write pos指標會順時針推進,當然基本不會與check point指標重合,因為MySQL有這種機制去實現,每次觸發檢查點checkpoint,check point會指標向前推進,這個過程就是需要進行刷日記和資料磁碟,記錄相應的LSN,引出難點LSN。

 

6.7、Redo Log 檢查點

啥時候會觸發檢查點checkpoint,網上找了點資料:啥時候資料庫會觸發檢查點checkpoint

圖6:檢查點觸發時機

Checkpoint發生的時間、條件及髒頁的選擇等都非常複雜。而Checkpoint所做的事情無外乎是將緩衝池中的髒頁刷回到磁碟,不同之處在於每次重新整理多少頁到磁碟,每次從哪裡取髒頁,以及什麼時間觸發Checkpoint。這些本文不會去研究。

 

6.8 Redo Log LSN

LSN這個概念,比較複雜,我介紹完你們不一定懂!LSN稱為紀錄檔的邏輯序列號(log sequence number),在innodb儲存引擎中,lsn佔用8個位元組。LSN的值會隨著紀錄檔的寫入而逐漸增大。可以簡單理解SLN就是記錄從開始到現在已經產生了多少位元組的Redo log值

儲存方式兩個指標又是通過LSN計算得到指向位置,因為LSN記錄的是檔案的大小位元組,當超過檔案大小時,需要用取模計算出這兩個指標位置,取模使得寫入就會從頭開始寫,這樣使得兩個指標在一個檔案中,一直落在迴圈位置,你追我趕的過程。這就是Redo log 環形邏輯思想設計實現。

 

上面提到LSN比較複雜,是因為它有很多個值,輸入命令"show engine innodb status;",可以看到四個的lsn記錄

圖7:LSN值列表

為了方便識別,我都為它們重新命名,如下。名詞記不住,後面無法繼續深入

1、記憶體日記:redo log buffer lsn;磁碟日記:redo log file lsn;

一般關係為:redo log buffer lsn >= redo log file lsn,如果刷盤時機為1,則redo log buffer lsn = redo log file lsn。

2、記憶體資料頁:data buffer lsn;資料磁碟資料頁:data disk lsn;

一般關係為data buffer lsn > data disk lsn,如果已經刷入資料磁碟,則data buffer lsn = data disk lsn。

3、檢查點:chckpoint lsn;

後面提到檢查點刷盤,資料刷盤和日記刷盤(如果有日記刷盤:則說明我假設的日記刷盤的時機設定值不為1,為1是同步的,即始終redo log buffer lsn = redo log file lsn,不會由檢查點觸發刷日記磁碟)。

 

都說Redo log是環形記錄,那麼怎麼記錄的?下面結合LSN給出記錄過程虛構圖,可以對比6.6 Redo log 儲存方式圖
相關知識:日記磁碟redo log file lsn + checkpoint lsn + 雙指標(write pos、check point)

1-8按時間順序發生。1點是假設最初的狀態;2、3點寫日記磁碟;4點是觸發了檢查點checkpoint,進行刷盤,checkpoint lsn=1開始,刷盤結束並更新checkpoint lsn=512。在5點、6點已經刷過了一回圈記憶體、二回圈記憶體,從頭開始寫入log,兩個指標指向回到了頭部。第7點也是一個觸發checkpoint的過程。9點是假設沒有更新,最後達到平衡的結果,即記憶體中資料頁和日記都完成了刷盤。

圖8:Redo Log File儲存過程

 

整個流程:

在某些情況下,觸發checkpoint,觸發資料頁和紀錄檔頁刷盤,此時將記憶體中的髒資料---"資料髒頁"和"紀錄檔髒資料" 分別刷到資料磁碟和日記磁碟中,而且兩者刷盤速度不一樣。checkpoint會保護機制,當資料刷盤速度超過紀錄檔刷盤時,將會暫時停止資料刷盤,等待紀錄檔刷盤進度超過資料刷盤。

刷盤時,對於資料磁碟,全部都是在記憶體中,此時每次刷一個資料頁到記憶體更新資料頁也更新了data disk lsndata buffer lsn(在更新記憶體資料頁時,會更新data buffer lsn)

對於日記磁碟,除了要記錄checkpoint lsn的值為檢查點 checkpoint的值(必須在結束時 直接記錄一個值,速度很快),這裡是針對日記刷盤時機不是1(1是同步快取刷日記刷盤)時,並且日記還沒刷到日記磁碟需要觸發將快取中日記提前刷到日記磁碟中,此時會將redo buffer log刷到redo log file中也更新了redo log file lsnredo log buffer lsn 。

 

模擬檢查點觸發前後,整個流程變化,一個資料頁和日記,資料變化及lsn從179-180的變化圖(刷盤時機不為1)

 

6.9 Redo log 容災恢復過程與LSN

結合6.4 Redo log 容災恢復過程和6.8的LSN知識,再次細化6.4的Redo log恢復過程

重新啟動innodb時,Redo log完不完整,採用6.4知識過程。用Redo log恢復,啟動資料庫時,InnoDB會掃描資料磁碟的資料頁data disk lsn和紀錄檔磁碟中的checkpoint lsn。兩者相等則從checkpoint lsn點開始恢復,恢復過程是利用 redo log到buffer pool,直到checkpoint lsn等於redo log file lsn,則恢復完成。

如果checkpoint lsn 小於 data disk lsn,說明在檢查點觸發後還沒結束刷盤時資料庫宕機了。因為checkpoint lsn最新值是在資料刷盤結束後才記錄的,檢查點之後有一部分資料已經刷入資料磁碟,這個時候資料磁碟已經寫入部分的部分恢復將不會重做,直接跳到沒有恢復的lsn值開始恢復。

 

七、瞭解 ChangeBuffer

7.1 為啥提到ChangeBuffer

為啥本文我會提到ChangeBuffer呢,其實很多時候會將ChangeBuffer和Redo log搞混,兩者都是巧用記憶體,減少磁碟IO,為了不弄混我覺得有必要專門對這個進行一個講解。

 

7.2 ChangeBuffer概念及作用

下面是我對ChangeBuffer的簡單介紹

也就是說對於更新的操作,如果用到了ChangeBuffer,更新的資料所在的資料頁如果不在記憶體中,將不用去資料磁碟將資料頁讀到記憶體,而是將這一次操作記錄在ChangeBuffer中,ChangeBuffer 主要節省的則是隨機讀磁碟的 IO 消耗,下次讀取查詢等讀取資料頁時用上ChangeBuffer中的記錄即可。其實也是一種巧用記憶體的思想。

 

7.3 ChangeBuffer與Redo log區別

Redo log 主要節省的是隨機寫磁碟的 IO 消耗(轉成順序寫),而 ChangeBuffer 主要節省的則是隨機讀磁碟的 IO 消耗

這句話怎麼理解,看下面:

Redo log 與 ChangeBuffer(含磁碟持久化) 這2個機制,不同之處在於優化了整個變更流程的不同階段

先不考慮Redo log、ChangeBuffer機制,簡化抽象一個更新(insert、update、delete)流程:

  1. 從磁碟讀取待變更的行所在的資料頁,讀入記憶體頁中
  2. 對記憶體頁中的行,執行變更操作
  3. 將變更後的資料頁,寫入至資料磁碟中

其中,流程中的步驟1涉及隨機讀磁碟IO;步驟3涉及隨機寫磁碟IO;剛好對應ChangeBuffer和Redo log。

對那句話的理解答案:

  • ChangeBuffer機制,優化了步驟1——避免了隨機讀磁碟IO ,將不在記憶體中的資料頁的操作寫入ChangeBuffer中,而不是將資料頁從磁碟讀入記憶體頁中
  • Redo log機制, 優化了步驟3——避免了隨機寫磁碟IO,將隨機寫磁碟,優化為了順序寫磁碟(寫Redo log,確保crash-safe)

 

7.4 有沒有用到ChangeBuffer對於Redo log的區別

Redo log機制,為了保證crash-safe,一直都會用到。 有無用到ChangeBuffer機制,對於redo log這步的區別在於—— 用到了ChangeBuffer機制時,在Redo log中記錄的本次變更,是記錄new change buffer item相關的資訊,而不是直接的記錄物理頁的變更(文章中第八節都有體現這一過程)。 在我們mysql innodb中, ChangeBuffer機制不是一直會被應用到,僅當待操作的資料頁當前不在記憶體中,需要先讀磁碟載入資料頁時,ChangeBuffer才有用武之地。

 

7.5 ChangeBuffer的merge過程

除了存取這個資料頁會觸發 merge 外,系統有後臺執行緒會定期 merge。在資料庫正常關閉(shutdown)的過程中,也會執行 merge 操作。

merge過程做三步

  1. 從磁碟讀入資料頁到記憶體(老版本的資料頁);
  2. 從 change buffer 裡找出這個資料頁的 change buffer 記錄 (可能有多個),依次應用,得到新版資料頁;
  3. 寫 redo log。這個 redo log 包含了資料的變更和 change buffer 的變更。

 

八、日記大連貫U-R-B,一舉攻破拿下

前面分別講的是Binlog、Undo log和Redo log,下面將他們都串聯起來,在一些流程體現全部日記。

同樣,以一些最經典的更新語句例子展開說明。

8.1 製造演示資料

測試語句:插入語句+查詢語句,a欄位是普通索引

1、insert into ta(a,b) values(2,5),(7, 5)
2、select * from t where a in (2, 7)

假設原來的資料如下圖,資料頁page1在記憶體中,page2不在。插入的資料(2,5)落在page1,資料(7,5)落在page2中。

 

8.2 假設沒有日記和ChangeBuffer 示範

先不考慮所有日記及ChangeBuffer機制,簡化抽象一個更新insert流程

  1. 從磁碟讀取待變更的行所在的資料頁,讀入記憶體頁中
  2. 對記憶體頁中的行,執行變更操作
  3. 將變更後的資料頁,寫入至資料磁碟中

 

8.3 考慮所有日記和ChangeBuffer 示範--現有Innodb流程

過程是 兩階段提交-----日記刷盤------資料刷盤(涉及Redo log lsn 和 ChangeBuffer的內容)

8.3.1 兩階段提交過程

  1. 資料(2,5)所在頁page1在記憶體中直接更新記憶體;資料(7,5)所在頁page2不在記憶體中,記錄change buffer(具有唯一性的索引或者沒有使用change buffer的操作是將磁碟中的資料頁讀入記憶體中並做更新)。
  2. 寫undo日記。先寫快取,後面根據刷盤引數決定何時刷入磁碟,後面的redo/Binlog都一樣。日記刷盤 在每一個日記中基本已經提到,它和設定的引數有關,具體刷盤可以參考上文4.3和6.5章節,下文不會再展開介紹。
  3. 寫redo日記(先記在記憶體中的更新,然後記不在記憶體中的change buffer的改變)
  4. 日記狀態改成prepare階段。
  5. 寫Binlog日記。
  6. 提交事務,日記狀態改成commit階段。

 

 

8.3.2 merge 過程

緊接著上文,圖片可上下參考,假設現在執行查詢語句 「select * from t where a in (2, 7)」 ,此次查詢索引a=7所在的資料頁不在記憶體中,並且上一步更新已經在change buffer中有記錄,將會觸發merge過程(參考第七章節7.5)。

  1. 將page2讀入記憶體
  2. 依次應用change buffer中的記錄,得到最新版資料頁
  3. 寫入redo,之前記錄的changebuffer改動,現在改成資料頁的改動

至於changebuffer被應用後是刪除還是標記,還有redo中原有的記錄changebuffer的改動怎麼調整是刪除還是修改成資料頁的改動這裡下面的圖是按照自己的想法描述出來,如有誤望留言指正。

 

8.3.3 資料刷盤過程

資料刷盤flush的有四種情況

  1. InnoDB 的 redo log 寫滿了。這時候系統會停止所有更新操作,把 checkpoint 往前推進,redo log 留出空間可以繼續寫
  2. 系統記憶體不足。當需要新的記憶體頁,而記憶體不夠用的時候,就要淘汰一些資料頁,空出記憶體給別的資料頁使用。如果淘汰的是「髒頁」,就要先將髒頁寫到磁碟
  3. MySQL 認為系統「空閒」的時候
  4. MySQL 正常關閉的情況

資料刷盤也代表著Redo log檢查點checkpoint觸發,這一步將聯絡到第六章6.7和6.8中的章節內容,較為複雜。

假設資料刷盤flush的四種情況發生了一種,那麼聯絡上文的過程將如下

  1. 將髒頁從記憶體中刷回到資料磁碟
  2. 刷完後更新檢查點checkpoint的值

流程中間某個環節資料庫宕機後,恢復具體過程,這些留在心裡了,沒往上去寫,讀者可以自行思考,不難。

 

九、結尾

整個文章講了Binlog、Undo log和Redo log,隨帶一提ChangeBuffer,前面四五六七章是分講,最後第八章是對整個日記相關聯講解。對此,講到這裡,基本上要把我要將的已經講完,內容挺多,有耐心可以慢慢啃,不懂歡迎留言!

思考環節,下面留下兩個問題,歡迎大家留言解答

1、為啥Binlog沒有crash-safe功能?

2、保證crash-safe為啥要用兩個日記,不能用一個日記嗎(Redo log或Binglog)?