MySQL 整體架構一覽

  • 2020 年 3 月 15 日
  • 筆記

MySQL 在整體架構上分為 Server 層和存儲引擎層。其中 Server 層,包括連接器、查詢緩存、分析器、優化器、執行器等,存儲過程、觸發器、視圖和內置函數都在這層實現。數據引擎層負責數據的存儲和提取,如 InnoDB、MyISAM、Memory 等引擎。在客戶端連接到 Server 層後,Server 會調用數據引擎提供的接口,進行數據的變更。

img

連接器

負責和客戶端建立連接,獲取用戶權限以及維持和管理連接。

通過 show processlist; 來查詢連接的狀態。在用戶建立連接後,即使管理員改變連接用戶的權限,也不會影響到已連接的用戶。默認連接時長為 8 小時,超過時間後將會被斷開。

簡單說下長連接:

優勢:在連接時間內,客戶端一直使用同一連接,避免多次連接的資源消耗。

劣勢:在 MySQL 執行時,使用的內存被連接對象管理,由於長時間沒有被釋放,會導致系統內存溢出,被系統kill. 所以需要定期斷開長連接,或執行大查詢後,斷開連接。MySQL 5.7 後,可以通過 mysql_rest_connection 初始化連接資源,不需要重連或者做權限驗證。

查詢緩存

當接受到查詢請求時,會現在查詢緩存中查詢(key/value保存),是否執行過。沒有的話,再走正常的執行流程。

但在實際情況下,查詢緩存一般沒有必要設置。因為在查詢涉及到的表被更新時,緩存就會被清空。所以適用於靜態表。在 MySQL8.0 後,查詢緩存被廢除。

分析器

詞法分析:

如識別 select,表名,列名,判斷其是否存在等。

語法分析:

判斷語句是否符合 MySQL 語法。

優化器

確定索引的使用,join 表的連接順序等,選擇最優化的方案。

執行器

在具體執行語句前,會先進行權限的檢查,通過後使用數據引擎提供的接口,進行查詢。如果設置了慢查詢,會在對應日誌中看到 rows_examined 來表示掃描的行數。在一些場景下(索引),執行器調用一次,但在數據引擎中掃描了多行,所以引擎掃描的行數和 rows_examined 並不完全相同。

不預先檢查權限的原因:如像觸發器等情況,需要在執行器階段才能確定權限,在優化器階段無法驗證。

MySQL 日誌模塊

如前面所說,MySQL 整體分為 Server 層和數據引擎層,而每層也對應了自己的日誌文件。如果選用的是 InnoDB 引擎,對應的是 redo log 文件。Server 層則對應了 binlog 文件。至於為什麼存在了兩種日誌系統,咱們往下看。

redo log

redo log 是 InnoDB 特有日誌,為什麼要引入 redo log 呢,想像這樣一個場景,MySQL 為了保證持久性是需要把數據寫入磁盤文件的。我們知道,在寫入磁盤時,會進行文件的 IO,查找操作,如果每次更新操作都這樣的話,整體的效率就會特別低,根本沒法使用。

既然直接寫入磁盤不行,解決方法就是先寫進內存,在系統空閑時再更新到磁盤就可以了。但光更新內存不行,假如系統出現異常宕機和重啟,內存中沒有被寫入磁盤的數據就會被丟掉,數據的一致性就出現問題了。這時 redo log 就發揮了作用,在更新操作發生時,InnoDb 會先寫入 redo log 日誌(記錄了數據發生了怎麼樣的改變),然後更新內存,最後在適當的時間再寫入磁盤。先寫日誌,在寫磁盤的操作,就是常說到的 WAL (Write-Ahead- Logging)技術。

redo log 的出現,除了在效率上有了很大的改善,還保證了 MySQL 具有了 crash-safe 的能力,在發生異常情況下,不會丟失數據。

在具體實現上 redo log 的大小是固定的,可配置一組為 4 個文件,每個文件 1GB,更新時對四個文件進行循環寫入。

img

write pos 記錄當前寫入的位置,寫完就後移,當第寫入第 4 個文件的末尾時,從第 0 號位置重新寫入。

check point 表示當前可以擦除的位置,當數據更新到磁盤時,check point 就向後移動。

write pos 和 check point 之間的位置,就是可以記錄更新操作的空間。當 write pos 追上 check point ,不在能執行新的操作,先讓 check point 去寫入一些數據。

可以將 innodb_flush_log_at_trx_commit 設置成 1,開啟 redo log 持久化的能力。

binlog

binlog 則是 Server 層的日誌,主要用于歸檔,在備份,主備同步,恢複數據時發揮作用,常見的日誌格式有 row, mixed, statement 三種。具體的使用方法可以參見 Binlog 恢復日誌這篇。

可以通過 sync_binlog=1 開啟 binlog 寫入磁盤。

這裡對 binlog 和 redo 進行下區分:

  1. 所有者不同,binlog 是 Server 層,所有引擎都可使用。redo log 是 InnoDB 特有的。
  2. 類型不同,binlog 是邏輯日誌,記錄的是語句的原始邏輯(比 statement)。redo log 是物理日誌,記錄某個數據頁被做了怎樣的修改。
  3. 數據寫入的方式不同,binog 日誌會一直追加,而 redo log 是循環寫入。
  4. 功能不同,binlog 用于歸檔,而 redo log 用於保證 crash-safe.

兩階段提交

一條更新語句,在 InnoDB 引擎下的更新過程如下。在更新內存後,將寫入 redolog 和寫入 binlog 放在一起成為一個事務最後一起寫入 redo log 和 binlog 的過程就是常說的兩階段提交。用於保證當有意外情況發生時,數據的一致性。

img

這裡假設下,如果不採用兩階段提交會發生什麼?

  1. 先寫 redo log 後寫 binlog. 假設在寫入 redo log 後,MySQL 發生異常重啟,此時 binlog 沒有寫入。在重啟後,由於 redolog 已經寫入,此時數據庫的內容是沒有問題的。但此時,如果想要拿 binlog 進行備份或恢復,發現會少了最後一條的更新邏輯,導致數據不一致。
  2. 先寫 binlog 和 redo log. binlog 寫入後,MySQL 異常重啟,redo log 沒有寫入。此時重啟後,發現 redo log 沒有成功寫入,任務這個事務無效,而此時 binlog 卻多了一條更新語句,拿去恢復後自然數據也是不一致的。

再分析下兩階段提交的過程:

  1. 在寫 redo log prepare階段奔潰,重啟後,發現 redo log 沒寫入,發現此次事務。
  2. 如果在寫 binlog 時奔潰,重啟後,發現 binlog 未被寫入,回滾操作
  3. 如果在寫入 redo log 和 binlog 後崩潰,重啟後,發現沒提交,則進行 commit.

小節

在文章開始部分,說明了 MySQL 的整體架構分為 Server 層和引擎層,並簡要說明了一條語句的執行過程。接着 MySQL 在 5.5 後選用 InnoDB 作為默認的引擎,就是因為比原生的 MyISAM 多了事務以及 crash-safe 的能力。

而 crash-safe 就是由 redo log 實現的。與 redo log 類似的日誌文件還有 binlog,是 Server 引擎的日誌,用于歸檔和備份數據。

最後提到了,為了保證數據的一致性,將 redo log 和 binlog 放入相同的事務中,也就是常提到的兩階段提交操作。