淺析 InnoDB 儲存引擎的工作流程
作者 | 楊洋
InnoDB
InnoDB 是由 Innobase Oy 公司開發,該儲存引擎是第一個完整支援 ACID 事務的 SQL/">MySQL 儲存引擎。具有 插入快取
、 兩次寫
、 自適應雜湊索引
等關鍵特性,是一個高效能、高可用的儲存引擎。
整體架構
InnoDB 有多個記憶體塊,這些記憶體塊組合在一起組成了一個大的記憶體池。而 InnoDB 的記憶體池中會有多個後臺執行緒,這些後臺執行緒負責重新整理記憶體池中的資料,和將髒頁(已修改的資料頁)重新整理到磁碟檔案。
後臺執行緒
預設情況下,InnoDB 儲存引擎有 13 個後臺執行緒:
-
一個 master 執行緒
-
一個鎖監控執行緒
-
一個錯誤監控執行緒
-
十個 IO 執行緒
-
插入快取執行緒
-
日誌執行緒
-
讀執行緒(預設 4 個)
-
寫執行緒(預設 4 個)
下面是我本機上的十個 IO 執行緒
-------- FILE I/O -------- I/O thread 0 state: waiting for i/o request (insert buffer thread) I/O thread 1 state: waiting for i/o request (log thread) I/O thread 2 state: waiting for i/o request (read thread) I/O thread 3 state: waiting for i/o request (read thread) I/O thread 4 state: waiting for i/o request (read thread) I/O thread 5 state: waiting for i/o request (read thread) I/O thread 6 state: waiting for i/o request (write thread) I/O thread 7 state: waiting for i/o request (write thread) I/O thread 8 state: waiting for i/o request (write thread) I/O thread 9 state: waiting for i/o request (write thread) Pending normal aio reads: 0 [0, 0, 0, 0] , aio writes: 0 [0, 0, 0, 0] , ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0 Pending flushes (fsync) log: 0; buffer pool: 0 540 OS file reads, 89 OS file writes, 7 OS fsyncs 0.00 reads/s, 0 avg bytes/read, 0.00 writes/s, 0.00 fsyncs/s
記憶體池
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
最大髒頁比例是通過配置檔案決定的,你可以根據實際情況來調整你自己的最大髒頁比例,來達到最好的效能。
虛擬碼如下:
for (int i = 0; i<10; i++) { thread_sleep(1) do log buffer flush to disk if ( last_one_second_ios < 5) { do merge at most 5 insert buffer } if (buf_get_modified_ratio_pct > innodb_max_ditry_pages_pct) { do buffer pool flush 100 dirty page } if (no user activity) { goto background loop } } if (last_ten_second_ios < 200) { do buffer pool flush 100 dirty page } do merge at most 5 insert buffer do log buffer flush to disk do full pourge if (buf_get_modifued_ratio_pct > 70%) { do buffer pool flush 100 dirty page } else { buffer pool flush 10 dirty page } do fuzzy checkpoint goto loop
後臺迴圈
在後臺迴圈中 InnoDB 會做這些事:刪除無用的Undo頁、合併插入快取。如果當前 InnoDB 處於空閒狀態,則跳轉到重新整理迴圈,否則跳轉到主迴圈繼續處理資料。
虛擬碼如下:
do full purge do merge 20 insert buffer if (not idle) { goto loop } else { goto flush loop }
重新整理迴圈
一旦執行到重新整理迴圈,InnoDB 會一直處理髒頁資料,直到髒頁資料達到最大髒頁比例以下。這時候會跳轉到暫停迴圈中(所有資料都處理完畢)。
虛擬碼如下:
flush loop: do buffer pool flush 100 dirty page if (buf_get_modified_ratio_pct > innodb_max_dirty_pages_pct) { goto flush loop } else { goto suspend loop }
暫停迴圈
在本迴圈中,InnoDB會將 Master Thread 掛起,減少記憶體資源使用,一直處於 waiting 狀態,等待事件來喚醒。一旦有新的事件過來,就跳轉到主迴圈中。
虛擬碼如下:
suspend loop: suspend_thread() waiting event goto loop;
由此可以看出,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 儲存引擎》這本書深入瞭解。
全文完
以下文章您可能也會感興趣:
-
ConcurrentHashMap 的 size 方法原理分析
-
從 ThreadLocal 的實現看雜湊演算法
-
所謂 Serverless,你理解對了嗎?
-
JVM 揭祕: 一個 class 檔案的前世今生
我們正在招聘 Java 工程師,歡迎有興趣的同學投遞簡歷到 [email protected] 。
杏仁技術站
長按左側二維碼關注我們,這裡有一群熱血青年期待著與您相會。