現在的位置: 首頁 > 資料庫 > 正文

MySQL 的 InnoDB 存儲引擎是怎麼設計的?

2020年02月21日 資料庫 ⁄ 共 2747字 ⁄ 字型大小 評論關閉

  MySQL 中的兩個成員 binlog 和 redo log。然而,這只是 MySQL 家族裡的兩個小嘍啰,Mysql 可以做到高性能高可靠,靠的絕對不只有他們倆。

  MySQL 里還有什麼其他成員呢?

  這其中,最底下的存儲引擎層(Storage Engines),它決定了 MySQL 會怎樣存儲數據,怎樣讀取和寫入數據,也在很大程度上決定了 MySQL 的讀寫性能和數據可靠性。

  對於這麼重要的一層能力,MySQL 提供了極強的擴展性,你可以定義自己要使用什麼樣的存儲引擎:InnoDB、MyISAM、MEMORY、CSV,甚至可以自己開發一個存儲引擎然後使用它。

  通常我們說 Mysql 高性能高可靠,都是指基於 InnoDB 存儲引擎的 Mysql,所以,這一講,先讓我們來看看,除了 redo log,InnoDB 里還有哪些成員,他們都有什麼能力,承擔了什麼樣的角色,他們之間又是怎麼配合的?

  InnoDB 內存架構

  InnoDB 主要分為兩大塊:內存和磁碟,讓我們先從內存開始。

  1、Buffer Pool

  正如之前提到的,MySQL 不會直接去修改磁碟的數據,因為這樣做太慢了,MySQL 會先改內存,然後記錄 redo log,等有空了再刷磁碟,如果內存里沒有數據,就去磁碟 load。

  而這些數據存放的地方,就是 Buffer Pool。

  我們平時開發時,會用 redis 來做緩存,緩解資料庫壓力,其實 MySQL 自己也做了一層類似緩存的東西。

  MySQL 是以「頁」(page)為單位從磁碟讀取數據的,Buffer Pool 里的數據也是如此,實際上,Buffer Pool 是a linked list of pages,一個以頁為元素的鏈表。

  為什麼是鏈表?因為和緩存一樣,它也需要一套淘汰演算法來管理數據。

  Buffer Pool 採用基於 LRU(least recently used) 的演算法來管理內存。

  2、Change Buffer

  上面提到過,如果內存里沒有對應「頁」的數據,MySQL 就會去把數據從磁碟里 load 出來,如果每次需要的「頁」都不同,或者不是相鄰的「頁」,那麼每次 MySQL 都要去 load,這樣就很慢了。

  於是如果 MySQL 發現你要修改的頁,不在內存里,就把你要對頁的修改,先記到一個叫 Change Buffer 的地方,同時記錄 redo log,然後再慢慢把數據 load 到內存,load 過來後,再把 Change Buffer 里記錄的修改,應用到內存(Buffer Pool)中,這個動作叫做 merge;而把內存數據刷到磁碟的動作,叫 purge:

  merge:Change Buffer -> Buffer Pool

  purge:Buffer Pool -> Disk

  上面是 MySQL 官網對 Change Buffer 的定義,仔細看的話,你會發現裡面提到:Change Buffer 只在操作「二級索引」(secondary index)時才使用,原因是「聚簇索引」(clustered indexes)必須是「唯一」的,也就意味著每次插入、更新,都需要檢查是否已經有相同的欄位存在,也就沒有必要使用 Change Buffer 了;另外,「聚簇索引」操作的隨機性比較小,通常在相鄰的「頁」進行操作,比如使用了自增主鍵的「聚簇索引」,那麼 insert 時就是遞增、有序的,不像「二級索引」,訪問非常隨機。

  3、Adaptive Hash Index

  MySQL 索引,不管是在磁碟里,還是被 load 到內存後,都是 B+ 樹,B+ 樹的查找次數取決於樹的深度。你看,數據都已經放到內存了,還不能「一下子」就找到它,還要「幾下子」,這空間犧牲的是不是不太值得?

  尤其是那些頻繁被訪問的數據,每次過來都要走 B+ 樹來查詢,這時就會想到,我用一個指針把數據的位置記錄下來不就好了?

  這就是「自適應哈希索引」(Adaptive Hash Index)。自適應,顧名思義,MySQL 會自動評估使用自適應索引是否值得,如果觀察到建立哈希索引可以提升速度,則建立。

  4、Log Buffer

  Log Buffer 里的 redo log,會被刷到磁碟里。

  Operating System Cache

  在內存和磁碟之間,你看到 MySQL 畫了一層叫做 Operating System Cache 的東西,其實這個不屬於 InnoDB 的能力,而是操作系統為了提升性能,在磁碟前面加的一層高速緩存,這裡不展開細講,感興趣的同學可以參考下維基百科:Page Cache

  InnoDB 磁碟架構

  磁碟里有什麼呢?除了表結構定義和索引,還有一些為了高性能和高可靠而設計的角色,比如 redo log、undo log、Change Buffer,以及 Doublewrite Buffer 等等。

  1、表空間(Tablespaces)

  可以看到,Tablespaces 分為五種:The System Tablespace;File-Per-Table Tablespaces;General Tablespace;Undo Tablespaces;Temporary Tablespaces。

  其中,我們平時創建的表的數據,可以存放到 The System Tablespace 、File-Per-Table Tablespaces、General Tablespace 三者中的任意一個地方,具體取決於你的配置和創建表時的 sql 語句。

  2、Doublewrite Buffer

  如果說 Change Buffer 是提升性能,那麼 Doublewrite Buffer 就是保證數據頁的可靠性。

  前面提到過,MySQL 以「頁」為讀取和寫入單位,一個「頁」裡面有多行數據,寫入數據時,MySQL 會先寫內存中的頁,然後再刷新到磁碟中的頁。

  這時問題來了,假設在某一次從內存刷新到磁碟的過程中,一個「頁」刷了一半,突然操作系統或者 MySQL 進程奔潰了,這時候,內存里的頁數據被清除了,而磁碟里的頁數據,刷了一半,處於一個中間狀態,不尷不尬,可以說是一個「不完整」,甚至是「壞掉的」的頁。

  有同學說,不是有 Redo Log 么?其實這個時候 Redo Log 也已經無力回天,Redo Log 是要在磁碟中的頁數據是正常的、沒有損壞的情況下,才能把磁碟里頁數據 load 到內存,然後應用 Redo Log。而如果磁碟中的頁數據已經損壞,是無法應用 Redo Log 的。

  所以,MySQL 在刷數據到磁碟之前,要先把數據寫到另外一個地方,也就是 Doublewrite Buffer,寫完後,再開始寫磁碟。Doublewrite Buffer 可以理解為是一個備份(recovery),萬一真的發生 crash,就可以利用 Doublewrite Buffer 來修復磁碟里的數據。

抱歉!評論已關閉.