1. 程式人生 > 資料庫 >MySQL 整體架構介紹

MySQL 整體架構介紹

MySQL 在整體架構上分為 Server 層和儲存引擎層。其中 Server 層,包括聯結器、查詢快取、分析器、優化器、執行器等,儲存過程、觸發器、檢視和內建函式都在這層實現。資料引擎層負責資料的儲存和提取,如 InnoDB、MyISAM、Memory 等引擎。在客戶端連線到 Server 層後,Server 會呼叫資料引擎提供的介面,進行資料的變更。

MySQL 整體架構介紹

聯結器

負責和客戶端建立連線,獲取使用者許可權以及維持和管理連線。

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

簡單說下長連線:

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

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

查詢快取

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

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

分析器

詞法分析:

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

語法分析:

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

優化器

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

執行器

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

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

使用 profiling 檢視 SQL 執行過程

開啟 profiling 分析語句執行過程:

mysql> select @@profiling;
+-------------+
| @@profiling |
+-------------+
|      0 |
+-------------+
1 row in set,1 warning (0.00 sec)
mysql> set profiling=1;
Query OK,0 rows affected,1 warning (0.00 sec)

執行查詢語句:

mysql> SELECT * FROM s limit 10;
+------+--------+-----+-----+
| s_id | s_name | age | sex |
+------+--------+-----+-----+
|  1 | z   | 12 |  1 |
|  2 | s   | 14 |  0 |
|  3 | c   | 14 |  1 |
+------+--------+-----+-----+
3 rows in set (0.00 sec)

獲取 profiles;

mysql> show profiles;
+----------+------------+--------------------------+
| Query_ID | Duration  | Query          |
+----------+------------+--------------------------+
|    1 | 0.00046600 | SELECT * FROM s limit 10 |
+----------+------------+--------------------------+

mysql> show profile;
+----------------------+----------+
| Status        | Duration |
+----------------------+----------+
| starting       | 0.000069 |
| checking permissions | 0.000008 | 許可權檢查
| Opening tables    | 0.000018 | 開啟表
| init         | 0.000019 | 初始化
| System lock     | 0.000010 | 鎖系統
| optimizing      | 0.000004 | 優化查詢
| statistics      | 0.000013 |
| preparing      | 0.000094 | 準備
| executing      | 0.000016 | 執行
| Sending data     | 0.000120 |
| end         | 0.000010 |
| query end      | 0.000015 |
| closing tables    | 0.000014 |
| freeing items    | 0.000032 |
| cleaning up     | 0.000026 |
+----------------------+----------+
15 rows in set,1 warning (0.00 sec)

查詢具體的語句:

mysql> show profile for query 1;
+----------------------+----------+
| Status        | Duration |
+----------------------+----------+
| starting       | 0.000069 |
| checking permissions | 0.000008 |
| Opening tables    | 0.000018 |
| init         | 0.000019 |
| System lock     | 0.000010 |
| optimizing      | 0.000004 |
| statistics      | 0.000013 |
| preparing      | 0.000094 |
| executing      | 0.000016 |
| Sending data     | 0.000120 |
| end         | 0.000010 |
| query end      | 0.000015 |
| closing tables    | 0.000014 |
| freeing items    | 0.000032 |
| cleaning up     | 0.000026 |
+----------------------+----------+
15 rows in set,1 warning (0.00 sec)

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,更新時對四個檔案進行迴圈寫入。

MySQL 整體架構介紹

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 執行 Update 時內部流程:

以更新 update T set c=c+1 where ID=2; 語句為例:

  1. 執行器通過 InooDB 引擎去 ID 所在行,ID 為主鍵。引擎通過樹搜尋找到該行,如果該行所在資料頁在記憶體中,返回給執行器。否則先從磁碟讀入記憶體,然後再返回。
  2. 執行器拿到引擎給的資料,將 C 值加 1,等到新的一行,然後通過引擎介面重新寫入新資料。
  3. 引擎將該行更新到記憶體中,同時將該更新操作記錄到 redo log 中,並更改 redo log 的狀態為 prepare 狀態。然後告知執行器,在合適的時間提交事務。
  4. 執行器生成這個操作的 binlog,並將 binlog 寫入磁碟。
  5. 執行器呼叫引擎到的提交事務介面,將剛剛寫入的 redo log 改成 commit 狀態,更新完成。

MySQL 整體架構介紹

淺色為執行器執行,深色為引擎執行。

在更新記憶體後,將寫入 redo log 拆分了成兩個步驟:prepare 和 commit,就是常說的兩階段提交。用於保證當有意外情況發生時,資料的一致性。

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

  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 階段奔潰,時刻 A 的位置。重啟後,發現 redo log 沒寫入,回滾此次事務。

2.如果在寫 binlog 時奔潰,重啟後,發現 binlog 未被寫入,回滾操作。

3.binlog 寫完,但在提交 redo log 的 commit 狀態時發生 crash

  • 如果 redo log 中事務完整,有了 commit 標識,直接提交。
  • 如果 redo log 中只有完整的 prepare,判斷對應 binlog 是否完整。

完整,提交事務
不完整,回滾事務。


如何判斷 binlog 是否完整?

  • statement 格式 binlog,會有 COMMIT; 標識
  • row 格式的 binlog,會有 XID event. 標識
  • 在 5.6 後,還有 binlog-checksum 引數,驗證 binlog 正確性。

如何將 redo log 和 binlog 關聯表示同一個操作?

結構中有一個共同的資料欄位,XID. 在崩潰恢復時,會按順序掃描 redo log:

  • 如果有 prepare,又有 commit 的 redo log,直接提交。
  • 如果只有 prepare,沒有 commit 的 redo log,拿 XID 去 binlog 找對應的事務做判斷。

資料寫入後,最終落盤和 redo log 有無關係?

  • 對於正常執行的 instance 來說,記憶體中頁被修改後,和磁碟的資料頁不一致,稱為髒頁。而落盤的過程,是把記憶體中的資料頁寫入磁碟。
  • 對於 crash 場景,InnoDB 判斷一個數據頁是否丟失了更新,會將其讀到記憶體,然後讓 redo log 更新記憶體內容。更新完成後,記憶體頁就變成髒頁,然後回到第一種情況的狀態。

redo log buffer 和 redo log 的關係?

在一個事務的更新過程中,存在多個 SQL 語句,所以是要寫多次日誌的。
但在寫的過程中,生產的日誌要先儲存起來,但在 commit 前,不能直接寫到 redo log 中。
所以通過記憶體中 redo log buffer 先存 redo log 的日誌。在 commit 時,將 buffer 中的內容寫入 redo log.

總結

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

而 crash-safe 就是由 redo log 實現的。與 redo log 類似的日誌檔案還有 binlog,是 Server 引擎的日誌,用於歸檔和備份資料。

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

以上就是MySQL 整體架構介紹的詳細內容,更多關於MySQL 整體架構的資料請關注我們其它相關文章!