InnoDB
InnoDB 是由 Innobase Oy 公司開發,該存儲引擎是第一個完整支持 ACID 事務的 MySQL 存儲引擎。具有插入緩存、兩次寫、自適應哈希索引等關鍵特性,是一個高性能、高可用的存儲引擎。
整體架構
InnoDB 有多個內存塊,這些內存塊組合在一起組成了一個大的內存池。而 InnoDB 的內存池中會有多個后臺線程,這些后臺線程負責刷新內存池中的數據,和將臟頁(已修改的數據頁)刷新到磁盤文件。
后臺線程
默認情況下,InnoDB 存儲引擎有 13 個后臺線程:
- 一個 master 線程
- 一個鎖監控線程
- 一個錯誤監控線程
- 十個 IO 線程
- 插入緩存線程
- 日志線程
- 讀線程(默認 4 個)
- 寫線程(默認 4 個)
下面是我本機上的十個 IO 線程
內存池
InnoDB 存儲引擎的內存池包含:緩沖池、日志緩存池、額外內存池。這些內存的大小分別由配置文件中的參數決定。其中占比最大的是緩沖池,里面包含了數據緩存頁、索引、插入緩存、自適應哈希索引、鎖信息和數據字典。InnoDB 會在讀取數據庫數據的時候,將數據緩存到緩沖池中,而在修改數據的時候,會先把緩沖池中的數據修改掉,一旦修改過的數據頁就會被標記為臟頁,而臟頁則會被 master 線程按照一定的頻率刷新到磁盤中。日志緩存則是緩存了redo-log 信息,然后再刷新到 redo-log 文件中。額外內存池則是在對一些數據結構本身分配內存時會從額外內存池中申請內存,當該區域內存不足則會到緩沖池中申請。
Master Thread
InnoDB 存儲引擎的主要工作都在一個單獨的 Master Thread 中完成,其內部由四個循環體構成:主循環( loop )、后臺循環( background loop )、刷新循環( flush loop )、暫停循環( suspend loop )。具體工作流程如下圖所示:
主循環
主要負責將緩沖池中的日志文件刷新到磁盤中、合并插入緩存、刷新緩沖池中的臟頁數據到磁盤中、刪除無用的 Undo 頁、產生一個 checkpoint 。在主循環中會多次將臟頁刷新到磁盤中,但是有一些刷新任務總會執行,有一些則根據參數來判斷當前是否需要刷新。而這個參數 innodb_max_dirty_pages_pct 最大臟頁比例是通過配置文件決定的,你可以根據實際情況來調整你自己的最大臟頁比例,來達到最好的性能。
偽代碼如下:
后臺循環
在后臺循環中 InnoDB 會做這些事:刪除無用的Undo頁、合并插入緩存。如果當前 InnoDB 處于空閑狀態,則跳轉到刷新循環,否則跳轉到主循環繼續處理數據。
偽代碼如下:
刷新循環
一旦執行到刷新循環,InnoDB 會一直處理臟頁數據,直到臟頁數據達到最大臟頁比例以下。這時候會跳轉到暫停循環中(所有數據都處理完畢)。
偽代碼如下:
暫停循環
在本循環中,InnoDB會將 Master Thread 掛起,減少內存資源使用,一直處于 waiting 狀態,等待事件來喚醒。一旦有新的事件過來,就跳轉到主循環中。
偽代碼如下:
由此可以看出,master 線程的最大的工作內容就是刷新臟頁數據到磁盤了。這一步就是把緩存池中被修改的數據頁同步到磁盤中。而臟頁數據的刷新基本上都是由innodb_max_dirty_pages_pct 來控制的,所以當你的服務器處理能力比較強,給 InnoDB 分配的內存池比較大,這時候可能你的臟頁數據會很難達到最大臟頁比,這時候你的數據基本上都在緩沖池中,可能需要很長一段時間才會到數據庫磁盤文件中,也就是臟頁的刷新速度會很低(MySQL 5.1之前的版本默認是 90%,后面調整到 75%)。所以實際應用中可以根據自己內存和數據庫的讀寫量來設置這個最大臟頁比。對于一次刷新臟頁數量的設置,在 InnoDB Plugin 中有一個參數 innodb_adaptive_flushing自適應刷新,InnoDB 會根據產生的重做日志速度來計算出當前最適合的刷新臟頁數量。當然 InnoDB Plugin 中還有其它很多參數配置,合理利用這些配置可以極大的提升 InnoDB 存儲引擎的性能。
關鍵特性
前面說到 InnoDB 的三大特性分別為:插入緩存、兩次寫、自適應哈希索引。下面就簡單介紹下這三大特性。
插入緩存
當我插入一條數據,該數據只有一個 ID 索引(聚集索引:數據行的物理順序與列值的邏輯順序相同)的時候,并且 ID 是自增長的,這時候頁中的行記錄按照 ID 順序存放,所以只需要在最新頁插入數據即可。但是如果我的表有多個非聚集索引(該索引中索引的邏輯順序與磁盤上行的物理存儲順序不同),在插入的時候非聚集索引的插入不再是順序的,這時候要離散的訪問非聚集索引頁,導致插入性能變低。而插入緩存則在插入的時候判斷緩沖池中是否存在當前非聚集索引,如果存在則直接插入,否則先插入到一個緩存區,然后再通過 Master Thread 來合并插入緩存。這樣極大的提高了數據的寫性能。
兩次寫
兩次寫是為了解決在將緩沖池中的臟頁刷新到磁盤的過程中,操作系統出現故障,導致當前的臟頁部分寫失效的問題。通過兩次寫在下次恢復的時候,InnoDB 會根據兩次寫的結果來恢復數據。
原理:在刷新臟頁的時候,不是直接把臟頁數據刷新到磁盤,而是將臟頁先寫到一個大小為2M的內存緩存中,再將這個內存緩存數據同步到磁盤的共享表空間中。當全部都寫到共享表空間后,再將數據刷新到磁盤中。這樣如果發生了上面描述的情況,這時候數據會在共享表空間中有個備份,恢復的時候就可以使用共享表空間的數據。
如果有數據庫集群的情況下,master數據庫是一定要開啟兩次寫的,為了保證數據可靠性。而從數據庫可以通過參數 skip_innodb_doublewrite 來禁止兩次寫功能,來提高插入效率。
自適應哈希索引
InnoDB 會監控對表示的索引查找,如果發現可以通過對索引進行哈希來優化搜索。這時候會對當前的索引建立哈希索引。稱之為自適應哈希索引( AHI )。可以通過參數innodb_adaptive_hash_index 來禁用或啟用此特性。
小結
總體來說 InnoDB 的高性能體現在:插入數據的時候先保存在內存中,直接跟內存交互性能比較好,而且還有插入緩存優化,保證了高并發寫操作。高可用則表現在兩次寫特性,保證了機器宕機或者出故障的時候數據不會丟失。這里只是簡單介紹了一下 InnoDB 的工作流程和一些特性,當然 InnoDB 還有很多很多強大的功能,比如說事務、鎖、索引、算法等等有興趣的同學可以參考《 MySQL 技術內幕 InnoDB 存儲引擎》這本書深入了解。