1. 程式人生 > >MySQL日誌系統

MySQL日誌系統

空間 很難 崩潰恢復 兩個 重啟 sql語句 bold 情況 歸檔日誌

如果你學習過操作系統課程,那麽對於這部分的內容理解起來準是毫無壓力了。

上篇文章(MySQL基礎架構):一條查詢語句我們經過連接器、分析器、優化器、執行器等模塊,最後到達存儲引擎。

那麽一條更新語句是怎樣的呢?

mysql> create table T(ID int primary key,c int);

如果我們將ID=2的這一行的值加1,SQL語句是這樣的:

mysql> update T set c=c+1 where ID=2;

我們再來看一下MySQL基礎架構這張圖:

技術分享圖片

對於更新語句,同樣也會走一遍這樣的路程。

與查詢語句共同的部分:

①在執行語句之前我們會連接數據庫,這個時候就會使用到連接器。

②當一個表有更新的時候,跟這個表的有關的緩存就會失效,所以這條語句會把表T上的所有緩存全都清空。因此不建議使用查詢緩存。

③分析器通過詞法和語法解析知道這是一條更新語句。優化器決定要使用ID這個索引。

然後執行器負責具體執行,找到這一行,然後更新。

與查詢語句不同的地方:

更新流程涉及到兩個重要的日誌模塊,物理日誌redo log(重做日誌)和 邏輯日誌binlog(歸檔日誌)。

日誌模塊redo log

對於這個模塊,可以舉個形象的例子,在當飯店裏,飯店老板往往會拿一個小的臨時記賬本A記下賬目,然後在飯店打烊之後,拿出今天的賬目詳細的算一下,然後寫到大的賬本B上看看誰賒賬啊,今天的收益如何....

如果有人要賒賬或者是還賬的話,掌櫃的一般就會有兩種做法:

  • 一種做法是直接把總的大賬本B拿出來,把這次賒的賬加上去或者扣除掉。
  • 另外一種做法是先在小賬本A上記下這次的賬,等打烊之後在把大賬本B拿出來核算。

在飯店很忙的時候,老板都會選擇後者。

在MySQL中也存在這樣的問題,如果每一次的更新操作都要寫入磁盤裏面,然後磁盤也要找到相應的記錄那條記錄,然後在更新,整個過程的IO成本,查找的成本都很高,為了解決這個問題,MySQL使用WAL(Write Ahead Logging)技術,關鍵點在於先寫日誌,在寫磁盤。在上面的例子中,就是我們說的先在小賬本A上記下這次的賬,等打烊之後在把賬本B拿出來核算。

我們具體詳細的說一下過程:

當有一條記錄需要更新的時候,InnoDB(存儲引擎)會先把這個記錄寫到redo log(小賬本A)上,並且更新內存,這個時候更新就算完成了。等到系統比較空閑的時候,InnoDB引擎會在適當的時候,將這個操作記錄更新到磁盤中。

如果今天來吃飯的人並不是很多,那麽老板就可以等打烊之後在整理。如果今天人很多,小賬本A被我們寫滿了,那怎麽辦呢?這個時候,老板只好先放下手中的活兒,把小賬本A上的一部分記錄更新到大賬本B中,然後這寫記錄就會被清除掉,為記新賬騰出空間。

與此類似,InnoDB的redo log是固定大小的,比如可以配置一組為4個文件,每個文件是1GB,那麽這塊“小賬本A”總共就可以記錄4GB內容,從頭開始寫,寫到末尾就又回到開頭循環寫。如下圖:

技術分享圖片

write pos 是當前記錄的位置,一邊寫一邊後移,寫到三號文件末尾回到0號文件的開頭(如果學習了數據結構的話,這很容易實現的,循環單鏈表),checkpoint是當前要擦除的位置,也是往後推移並且循環的,擦除記錄前我們要把記錄更新到數據文件。

在writepos和checkpoint之間的“小賬本”上還空著的部分(在上圖中就是ib_logfile_0),可以用來記錄新的操作,如果write pos追上了checkpoint,表示的是"小賬本A"滿了,這個時候不能夠在執行新的更新,得停下來先擦出一些記錄。把checkpoint推進一下。

crash-safe: 有了redo log,InnoDB就可以保證即使數據庫發生異常重啟,之前提交的記錄都不會丟失。

有了crash-safe這個概念之後,我們就可以知道假如數據庫突然發生異常,恢復之後,我們仍然可以通過硬盤和redo log上的數據來明確操作了。

重要的日誌模塊:binlog

MySQL有兩塊組成:一塊是Server層,它主要的任務是MySQL功能層的事情,還有一塊是引擎層,負責存儲相關的具體事宜。

redo log是InnoDB引擎特有的日誌,而Server層有自己的日誌,稱為binlog(歸檔日誌)。

在之前MySQL中並沒有InnoDB引擎。MySQL自帶的引擎是MyISAM,但是MyISAM沒有crash-safe的能力。binlog日誌只能用於歸檔。而InnoDB是另一個公司以插件形式引入MySQL的,既然只依賴binlog沒有crash-safe能力,所以InnoDB使用另外一套日誌系統----也就是redo log來實現crash-safe能力。

binlog和redo log的不同點:

  1. redo log 是InnoDB引擎特有的;binlog是MySQL的Server層實現的,所有的引擎都可以使用。
  2. redo log是物理日誌,記錄的是“某個數據頁上做了什麽修改”;binlog是邏輯日誌,記錄的是這個語句的原始邏輯,就像這個語句“給ID=2這一行的c字段加1”。
  3. redo log是循環寫的,空間固定會用完;binlog可以追加(追加寫入指的是binlog文件寫到一定大小後會切換到下一個,不會覆蓋以前的日誌。這個很好理解,覆蓋了就很難找回來了,在邏輯上記錄。)寫入。

我們再來看一下執行器和InnoDB引擎在執行這個簡單的update語句時的內部流程。

  1. 執行器先找引擎ID=2這一行,ID是主鍵,引擎直接用樹搜索找到這一行。如果ID=2這一行所在的數據也本來就在內存中,就直接返回給執行器;否則,需要先從磁盤讀入內存,然後再返回。
  2. 執行器拿到引擎給的行數據,把這個值加上1,比如原來是N,現在就是N+1,得到新的一行數據,在調用引擎接口寫入這行新數據。
  3. 引擎將支行數據更新到內存中,同時將這個更新操作記錄到redo log裏面,此時redo log處於prepare狀態。然後告知執行器執行完了,隨時可以提交事務。
  4. 執行器生成這個操作的binlog,並把binlog寫入磁盤。
  5. 執行器調用引擎的事務接口,引擎把剛剛寫入的redo log改成提交(commit)狀態,更新完成。

下面的這張圖是update語句的執行流程圖,藍色框的表示實在InnoDB內部執行的,綠色框的表示是在執行器中執行的。

技術分享圖片

在這裏我們發現:將redo log的寫入拆成了兩個步驟:prepare 和commit,這就是"兩階段提交"。

個人的理解:

1 InnoDB引擎將支行數據更新到內存中,同時將這個更新操作記錄到redo log裏面,InnoDB引擎 redo log處在prepare階段

2 執行器收到InnoDB引擎發來的消息“嘿,哥們,我OK了!”,執行器很開心的寫binlog,並將binlog存入磁盤。

3 引擎收到執行器通過事務接口發來的消息“我寫完了,該你了!”,InnoDB引擎把剛剛寫入的redo log改成提交(commit)狀態,更新完成。

為什麽要有兩階段提交呢?

如果不這樣做的壞處:

由於redo log和binlog是兩個獨立的邏輯,如果不用兩階段提交,那麽就是先寫完redo log再寫binlog ,或者采用反過來的順序。我們看看出現的問題:

仍然用前面的update語句來做例子。假設當前ID=2的行,字段c的值是0,再假設執行update語句過程 中在寫完第一個日誌後,第二個日誌還沒有寫完期間發生了crash,會出現什麽情況呢?

1. 先寫redo log後寫binlog。假設在redo log寫完,binlog還沒有寫完的時候,MySQL進程異常重 啟。由於我們前面說過的,redo log寫完之後,系統即使崩潰,仍然能夠把數據恢復回來,所以恢 復後這一行c的值是1。 但是由於binlog沒寫完就crash了,這時候binlog裏面就沒有記錄這個語句。因此,之後備份日誌的 時候,存起來的binlog裏面就沒有這條語句。 然後你會發現,如果需要用這個binlog來恢復臨時庫的話,由於這個語句的binlog丟失,這個臨時 庫就會少了這一次更新,恢復出來的這一行c的值就是0,與原庫的值不同。

2. 先寫binlog後寫redo log。如果在binlog寫完之後crash,由於redo log還沒寫,崩潰恢復以後這 個事務無效,所以這一行c的值是0。但是binlog裏面已經記錄了“把c從0改成1”這個日誌。所以,在 之後用binlog來恢復的時候就多了一個事務出來,恢復出來的這一行c的值就是1,與原庫的值不同。 可以看到,如果不使用“兩階段提交”,那麽數據庫的狀態就有可能和用它的日誌恢復出來的庫的狀態不一致。

兩階段恢復這樣做的好處:(接上面的個人理解)

  • 在2之前奔潰(就是說執行器還沒有寫binlog的時候)時

重啟恢復:後發現沒有commit,回滾,備份恢復:

沒有binlog.數據庫的狀態就有可能和用它的日誌恢復出來的庫的狀態一致。

  • 當在3之前奔潰

重啟恢復;雖沒有commit,但滿足prepare和binlog完整,所以重啟後會自動commit.備份:有binlog。

數據庫的狀態和用它的日誌恢復出來的庫的狀態一致。

應用場景:

在誤操作後用這個過程恢復數據,在你需要擴容的時候,也就是需要在多搭建一些備庫來增加系統的讀能力的時候,現在常用的做法就是用全量備份加上應用binlog來實現的,合格"不一致"就會導致你的線上出現主從數據庫不一致的情況。

簡單的說,redo log和binlog都可以用於表示事務的提交狀態,而兩階段提交就是讓這這兩個狀態保持邏輯上的一致。

總結:

物理日誌 redo log 邏輯日誌 binlog 作用:

redo log用於保證crash-safe能力。innodb_fush_log_at_trx_commit這個參數設置成1的時候,表示每次事務的redo log都直接持久化到磁盤。這個參數我建議你設置成1,這樣可以保證MySQL異常重啟之 後數據不丟失。 sync_binlog這個參數設置成1的時候,表示每次事務的binlog都持久化到磁盤。這個參數我也建議你設置成1,這樣可以保證MySQL異常重啟之後binlog不丟失。

MySQL日誌系統