借用前文mysql指引(二):mysql邏輯結構和整體處理流程的圖,我們再來回顧下mysql的基本結構:
主要有兩塊構成:- mysql server 層
- mysql 存儲引擎層
由於底層引擎層可以進行更換,故和軟件開發一樣,server層需要制定好接口定義,便於不同的接口實現(存儲引擎)接入。如下圖所示:
這樣就和我們平時開發軟件一樣,基於接口編程。對 server 層來說,屏蔽底層不同存儲引擎實現的細節。對於存儲引擎來說,根據接口定義,只要實現了接口,就意味着可以被融入到 mysql 的體系中。最終實現了插件式的存儲引擎。來看下官方是怎麼說的:
The storage engines manage data storage and index management for MySQL. The MySQL server communicates with the storage engines through a defined API.
Each storage engine is a class with each instance of the class communicating with the MySQL server through a special
handler
interface.
這裏的API即接口定義,我們來看幾個。
查找類方法:
index_first:Retrieve first row in index and return.
index_next:Return next row in index.
index_read:Find a row based on a key and return.
records_in_range:For the given range how many records are estimated to be in this range.Used by optimizer to calculate cost of using a particular index.
增刪改方法:update_row、delete_row、write_row
其中,優化器就是調用 records_in_range 方法來計算掃描行數的。
知道了上述這些方法後,感興趣可以更進一步,來定製屬於自己的存儲引擎。
下面要說的是關於 binlog
和 redo log
這兩種日誌。在 mysql server 層,引入了binlog來做歸檔日誌,主要可用於備份恢復,同時也被應用到主從同步等場景。在 mysql 引擎層,innodb引擎引入了 redo log,主要用來做崩潰恢復。
這塊內容關鍵要理解下面幾點:
- binlog的備份恢復是如何實現的
- redo log的崩潰恢復是如何實現的
- 爲什麼需要兩階段提交
- 刷盤
其他關於 binlog 的部分,會在分佈式 mysql 中繼續說明。
binlog日誌相關的網上有很多解釋,這裏只列舉幾個關鍵點。Mysql最初的引擎是myisam,Mysql 只在 server 層提供 binlog 記錄,存儲數據庫執行的操作日誌,起到歸檔作用。
binlog日誌有三種格式:
- statement格式:可理解爲直接記錄執行的sql語句
- row格式:可理解爲記錄對應的行數據
- Mix格式:就是前兩者的組合,會自動判斷採用哪種
當事務提交時,會將日誌內容按照一定格式追加寫入磁盤。不會覆蓋,所以可以作爲備份用。恢復時按照對應格式解析更新即可。
但是這裏面有一個問題,假如兩個事務A和B,期間都對記錄1執行了更新類的操作,假如A先更新記錄1,然後B再更新記錄1。A晚於B提交事務,那麼最終binlog中就會先記錄B的更新動作,然後纔是A的更新動作。
這樣,在從機上同步或者恢復時,就會出現問題。
但,真的會成這樣子嗎?實際上,前文學習了當前讀、鎖等概念後,你應該知道這裏的描述是錯誤的。事務B會在更新記錄1時被事務A阻塞,知道事務A提交爲止。
另外一個問題是,我們說的寫到磁盤,是真的落盤了嗎?實際上不一定。日誌寫到binlog中,無非是將日誌數據從Mysql的緩存(實際上爲 binlog cache)搬移到了操作系統的緩存中(實際上爲 page cache)。至於page cache中的數據何時落盤,就依賴操作系統的管理。
當然執行底層調用 fsync()
時就會將緩存數據寫入磁盤,纔會產生磁盤IO。
可以回顧下講解 innodb 基本結構的章節,裏面官方圖中,就有一個標註:O_DIRECT
,即直接寫入磁盤,不走操作系統緩存。
最後一個問題是,binlog的結構。binlog文件的組成單位是 binlog event
,每個binlog event 中包含了具體的日誌內容,這些內容格式就分爲了row、statement這種。實際上不需要了解 binlog event 的具體構成細節,類比數據頁結構即可。
來看幾個典型的binlog event:
- query event:在statement格式下,裏面包含了對應的sql語句;在row格式下,對應的是begin語句;
- gtid event:記錄GTID版本號(分佈式mysql會說到)
- xid event:當事務提交時,會記錄該事件。
那麼,redo log的作用呢?這點前文已經講過了,用來做宕機恢復。
關於binlog 和 redo log這塊,總會出現兩階段提交的討論。指的是redo log 先寫成 prepare 狀態,然後binlog寫入,最後redo log 變成commit狀態。
重點是爲什麼一定需要兩階段提交?不執行兩階段提交的話,主要矛盾點在於:
- redo log 做主機崩潰恢復
- binlog 做從機同步
如果 redo log 和 binlog 不同步,那麼主機宕機恢復後,就和從機的同步數據不一致,這點是不能允許的。
redo log 和 binlog 都是記錄事務的,所以二者之間是根據事務ID關聯起來的。 binlog 中 類型爲 XID 的event 中就包含了事務ID,和redo log 中的事務ID一致。
所以,假如 redo log 最後是 prepare 狀態,那麼崩潰恢復時,會根據 redo 中某個事務的事務ID,去 binlog 中查找對應的事務,判斷該事務是否具有XID event,不具有則說明該事務未能正確提交,故無需恢復本事務。
最後,我們來簡單看下崩潰恢復的整體流程:
- 回滾未能 prepare 的事務
- 記錄已 prepare 但是未能 commit 的事務
- 前兩步就把 redo log 中的事務掃描並準備好了
- 因爲 binlog 是追加寫入,而崩潰是發生在一瞬間的,故mysql會讀取最後一個binglog文件,並判斷是否需要執行崩潰恢復流程
- 如果需要恢復,則會依次掃描 binlog event,記錄已經提交的事務ID(即有 XID event)。
- 掃描結束後對比 redo log中的記錄的已 prepare,未commit的事務,將匹配到的事務執行 commit 操作,將未能匹配的執行回滾操作。
本篇主要目的是將 mysql 和 innodb 結合起來,針對 binlog 和存儲引擎接口進行說明。
接下來,就要開啓全新的篇章,即分佈式 mysql。從我們已經熟悉的 mysql 上手,理解分佈式領域的一些關鍵概念。