1. 程式人生 > >MySQL InnoDB 儲存引擎原理淺析

MySQL InnoDB 儲存引擎原理淺析

版權說明: 本文章版權歸本人及部落格園共同所有,轉載請標明原文出處( https://www.cnblogs.com/mikevictor07/p/12013507.html ),以下內容為個人理解,僅供參考。

 

前言:

本文主要基於MySQL 5.6以後版本編寫,多數知識來著書籍《MySQL技術內幕++InnoDB儲存引擎》,本文章僅記錄個人認為比較重要的部分,有興趣的可以花點時間讀原書。

今年的多數學習知識只寫在筆記裡,較為零散,最近稍有時間整理出來,分享進步。  

 

一、MySQL體系結構

主要包含以下幾部分:

1、管理服務於工具元件。

2、連線池與鑑權。

3、SQL介面。

4、查詢分析器。

5、優化器元件。

6、快取與緩衝區。

7、各式的外掛式儲存引擎。

8、物理檔案。

其中儲存引擎是基於表,而非資料庫。

 

二、InnoDB體系結構

 

InnoDB引擎包含幾個重要部分:

1、後臺程序:

    1.1 Master Thread:核心執行緒,負責緩衝池的資料非同步入盤,包括髒頁重新整理、合併插入緩衝、undo頁回收等。

    1.2 IO Thread:包括read thread 和writer thread,使用show variables like '%innodb_%io_thread%';檢視。

    1.3 Purge Thread:回收事務提交後不再需要的undo log,通過show variables like '%innodb_purge_threads%'; 檢視。

    1.4 Page clear thread:髒頁的重新整理操作,從master thread分離出來。

 

2、記憶體池

    2.1 緩衝池

InnoDB將記錄按頁的形式進行管理,對於頁的修改先修改緩衝池中的頁,後以一定頻率進行重新整理到磁碟中(checkpoint)。在資料庫的頁讀取操作時,將也快取到緩衝池中,下一次如讀取相同的頁,則無需從磁碟中載入。快取池大小通過innodb_buffer_pool_size配置。

從上圖來看,主要包括索引頁、資料頁、undo頁、insert buffer、adaptive hash index、資料字典等,其中索引頁和資料頁佔用多數記憶體。

    配置innodb_pool_buffer_instances將緩衝池分割為多個例項,減少內部競爭(比如鎖)。

 

    2.2 LRU list、free list、flush list

    預設的緩衝頁大小是16KB,使用LRU演算法進行管理,新從磁碟載入的頁預設加到LRU列表的midpoint處(尾端算起37%位置處)。通過show engine innodb status輸出如下(部分):

-------------------

Buffer pool size   512  【緩衝池記憶體512*16K】

Free buffers       256

Database pages     256  【LRU列表佔用頁】

Old database pages 0

Modified db pages  0

Pending reads      0

Pending writes: LRU 0, flush list 0, single page 0

Pages made young 0, not young 0

0.00 youngs/s, 0.00 non-youngs/s

Pages read 255, created 40, written 67

0.16 reads/s, 0.06 creates/s, 0.37 writes/s

Buffer pool hit rate 943 / 1000 【緩衝池命中率大於95%則良好】, young-making rate 0 / 1000 not 0 / 1000

LRU len: 256, unzip_LRU len: 0 【LRU列表中的頁可被壓縮分為1K/2K/4K/8K之類的頁】

------------------

LRU列表中的頁被修改後變為dirty page,此時緩衝池中的頁和磁碟不一致,通過checkpoint刷回磁碟,其中Flush list即為dirty page列表。

 

    2.3 Redo log buffer

    InnoDB將重做日誌首先刷入緩衝區中,後續以每秒一次重新整理到日誌檔案中,通過show variables like 'innodb_log_buffer_size'; 檢視,需要保證mysql每秒事務量應該小於此大小,通常可以配置8-32MB。以下情況會重新整理緩衝區到磁碟的重做日誌檔案中:

    1、Master thread每秒重新整理。

    2、每個事務提交。

    3、緩衝區空間小於1/2(如果緩衝區過小則導致頻繁的磁碟重新整理,降低效能)。

    

    2.4 innodb_additonal_mem_pool_size

    如果申請了很大的buffer pool,此引數應該相應增加,儲存了LRU、鎖等資訊。

 

3、checkpoint

    每次執行update、delete等語句更改記錄時,緩衝池中的頁與磁碟不一致,但是緩衝池的頁不能頻繁重新整理到磁碟中(頻率過大效能低),因此增加了write ahead log策略,當事務提交時先寫重做日誌,再修改記憶體頁。當發生宕機時通過重做日誌來恢復。checkpint解決以下問題:

    (1)減少重做日誌大小,縮減資料恢復時間。

    (2)緩衝池不夠用時將髒頁刷回磁碟。

    (3)重做日誌不可用時將髒頁刷回磁碟(如寫滿)。

 show variables like 'innodb_max_dirty_pages_pct'; (預設75%)來控制inndodb強制進行checkpoint。

 

若每個重做日誌大小為1G,定了了兩個總共2G,則:

    asyn_water_mark = 75 % * 重做日誌總大小。

    syn_water_mark = 90 % * 重做日誌總大小。

    (1)當checkpoint_age < asyn_water_mark時則不需要重新整理髒頁回盤。

    (2)當syn_water_mark < checkpoint_age < syn_water_mark 時觸發ASYNC FLUSH。

    (3)當checkpoint_age>syn_water_mark觸發sync flush,此情況很少發生,一般出現在大量load data或bulk insert時。

 

4、InnoDB關鍵特性

關鍵特性包括:

(1) Insert buffer.

(2) double write.

(3) adaptive hash index.

(4) Async IO.

(5)Flush neighbor page.

 

4.1  Insert buffer

若插入按照聚集索引primary key插入,頁中的行記錄按照primary存放,一般情況下不需要讀取另一個頁記錄,插入速度很快(如果使用UUID或者指定的ID插入而非自增型別則可能導致非連續插入導致效能下降,由B+樹特性決定)。如果按照非聚集索引插入就很有可能存在大量的離散插入,insert buffer對於非聚集索引的插入和更新操作進行一定頻率的合併操作,再merge到真正的索引頁中。使用insert buffer需滿足條件:

    (1)索引為輔助索引。

    (2)索引非唯一。(唯一索引需要從查詢索引頁中的唯一性,可能導致離散讀取)

 

4.2 Double write

Doubel write保證了頁的可靠性,Redo log是記錄對頁(16K)的物理操作,若innodb將頁寫回表時寫了一部分(如4K)出現宕機,則物理頁將會損壞無法通過redolog恢復。所以在apply重做日誌前,將緩衝池中的髒頁通過memcpy到doublewrite buffer中,再將doublewrite buffer頁分兩次每次1MB刷入共享表空間的磁碟檔案中(磁碟連續,開銷較小),完成doublewrite buffer的頁寫入後再寫入各個表空間的表中。

 

當寫入頁時發生系統崩潰,恢復過程中,innodb從共享表空間的doublewrite找到該頁的副本,並將其恢復到表空間檔案中,再apply重做日誌。

 

4.3 Adaptive hash index

    Innodb根據訪問頻率對熱點頁建立雜湊索引,AHI的要求是對頁面的訪問模式必須一樣,如連續使用where a='xxx' 訪問了100次。建立熱點雜湊後讀取速度可能能提升兩倍,輔助索引連線效能提升5倍。

通過show engine innodb status\G;檢視hash searches/s, 表示使用自適應雜湊,對於範圍查詢則不能使用。

 

4.4 Async IO

    使用者執行一次掃描如果需要查詢多個索引頁,可能會執行多個IO操作,AIO可同時發起多個IO請求,系統自動將這些IO請求合併(如請求資料頁[1,2]、[2,3]則可合併為從1開始連續掃描3個頁)提高讀取效能。

 

4.5 重新整理臨近頁

InnoDB提供重新整理臨近頁功能:當重新整理一髒頁時,同時檢測所在區(extent)的所有頁,如果有髒頁則一併重新整理,好處則是通過AIO特性合併寫IO請求,缺點則是有些頁不怎麼髒也好被重新整理,而且頻繁的更改那些不怎麼髒的頁又很快變成髒頁,造成頻繁重新整理。對於固態磁碟則考慮關閉此功能(將innodb_flush_neighbors設定為0)。

 

5、InnoDB的啟動、關閉與恢復

5.1 innodb_fast_shutdown

該值影響資料庫正常關閉時的行為,取值可以為0/1/2(預設為1):

【為0時】:關閉過程中需要完成所有的full purge好merge insert buffer,並將所有的髒頁重新整理回磁碟,這個過程可能需要一定的時間,如果是升級InnoDB則必須將此引數調整為0再關閉資料庫。

【為1時(預設)】:不需要full purge和merge insert buffer,但會將緩衝池中的髒頁寫回磁碟。

【為2時】:不需要full purge和merge insert buffer,也不會將緩衝池中的髒頁寫回磁碟,而是將日誌寫入日誌檔案中,後續啟動時recovery。

 

5.2 innodb_force_recovery

引數innodb_force_recovery直接影響InnoDB的恢復情況。

預設值為0:進行所有的恢復操作,當不能進行有效恢復(如資料頁corrupt)則將錯誤寫入錯誤日誌中。

 

某些情況下不需要完整的恢復造成,則可定製恢復策略,有以下幾種:

  • 1(SRV_FORCE_IGNORE_CORRUPT):忽略檢查到的corrupt頁。
  • 2(SRV_FORCE_NO_BACKGROUND):阻止Master Thread執行緒執行,如果master thread需要進行full purge操作,這樣會導致crash。
  • 3(SRV_FORACE_NO_TRX_UNDO):不進行事務的回滾操作。
  • 4(SRV_FORCE_NO_IBUF_MERGE):不進行插入緩衝區的合併操作。
  • 5(SRV_FORCE_NO_UNDO_LOG_SCAN):不檢視undo log,這樣未提交的事務被視為已提交。
  • 6(SRV_FORCE_NO_LOG_REDO):不進行redo操作。

 

在設定了innodb_force_recovery大於0後可對錶進行select/create/drop操作,但不能進行insert update和delete等DML。如有大事務未提交,並且發生了宕機,恢復過程緩慢,不需要進行事務回滾則將引數設定為3以加快啟動過程。

 

三、檔案

3.1 二進位制日誌

    二進位制日誌記錄MySQL的變更操作(不包含查詢),如果資料的影響行數為0也會記錄。主要用於資料的恢復、複製、審計等場景。通過log-bin引數配置binlog的檔名。影響二進位制日誌記錄的行為有:

(1) max_binglog_size

(2) binlog_cache_size

(3) sync_binlog

(4) binlog-to-db

(5) binlog-ignore-db

(6) log-slave-update

(7) binlog_format

 

max_binglog_size指定單個日誌檔案最大值,超過則產生新檔案,預設為1G。

binlog_cache_size預設為32K,記錄未提交的事務,當提交事務後會寫入二進位制日誌檔案中,該引數是基於會話的,不宜設定過大,通過以下命令檢查是否cache不夠導致使用到了磁碟(binlog_cache_disk_use),單位為次數:

$ show variables like 'binlog_cache_size';

$ show global status like 'binlog_cache%'; (該命令顯示的單位為次數)

如果顯示的binlog_cache_disk_use次數較多,則考慮要增加binlog_cache_size大小。

 

sync_binlog表示每寫多少次緩衝就同步到磁碟,通過設定引數為1則代表同步的方式寫磁碟,但即使將該引數設定為1,還有一種異常場景:假設事務發出commit前,由於sync_binlog設定為1會立即寫盤,但實際上還沒提交事務就宕機,下次重啟前由於沒有commit動作事務將會被回滾,但二進位制日誌記錄了該事務又不能被回滾,該異常場景通過設定innodb_support_xa為1來解決,保證了二進位制日誌與InnoDB儲存贏錢資料檔案的同步。

 

3.2 InnoDB儲存引擎檔案

3.2.1 表空間檔案

    預設共享表空間為ibatat1,可通過設定innodb_data_file_path=/db/ibdata1:2000M; /dir2/db/ibdata2:2000M:autoextend 指定多個共享表空間檔案(用於均衡磁碟負載),通過設定autoextend用完自動增長,該檔案不會縮小(即使刪除記錄),只能通過匯出資料後,再刪除該檔案後重啟再匯入才能縮小此檔案佔用的空間。

一般情況下開啟引數innodb_file_per_table=ON來開個獨立表空間,每個表都有自己的表空間,以:表名.idb 命名,在清空表會後自動釋放此單獨的表空間。

獨立的表空間僅儲存該表的資料、索引、插入緩衝BITMAP等資訊,其餘的資訊還是放在預設表空間中。

 

3.2.2 重做日誌檔案(Redo log file)

    MySQL預設初始化ib_logfile0、ib_logfile1兩個重做日誌檔案,一個用完切換到另一個,影響引數如下:

(1) innodb_log_file_size : 每個redo log檔案大小。

innodb_log_files_in_group : 檔案組中的檔案數量,預設為2.

innodb_mirrored_log_groups : 映象檔案組數量,預設為1,如果磁碟已做高可用陣列,則用預設的1即可,不再需要再做日誌映象。

innodb_log_group_home_dir : 日誌檔案路徑,預設在資料檔案路徑下。

 

Redo log設定不易過大,多大則重啟需要恢復時間很長,也不宜過小,過小則導致頻繁發生async checkpoint,需要刷髒頁回磁碟,影響效能。一般的應用設定為1G即可。

 

InnoDB中重做日誌是記錄每個page的物理更改情況,而二進位制檔案是僅在事務提交前提交(即只寫磁碟一次),在事務進行過程中,卻不斷有redo entry寫入到重做日誌檔案中。兩者是由差別的。

引數innodb_flush_log_at_trx_commit影響重做日誌的刷寫動作,有以下值:

【0】事務提交時並不寫,而是等待主執行緒每秒刷寫一次。

【1】預設值,表示執行事務commit時同步寫到磁碟,提供最大的安全性,也是最慢的方式。

【2】非同步寫磁碟,先寫到系統快取,交給系統寫到磁碟。

 

四、表

    表空間由segment、extend、page組成,其中page是InnoDB磁碟管理的最小單位(預設大小為16K)。如下圖:

 

如果啟用了innodb_file_per_table引數,每張表的表空間只存放資料、所以和插入緩衝bitmap頁,其他的資料如undo資訊、插入緩衝、double write buffer等還是存放在共享表空間中。

 

4.1 Segment (段)

    

常見的segment有資料段、索引段、回滾段等, 資料段為B+樹的葉子節點(Leaf node segment)、索引段為B+樹的非葉子節點(Non-leaf node segment)。如下圖:

 

4.2 Extend (區)

    每個區大小固定為1MB,為保證區中page的連續性通常InnoDB會一次從磁碟中申請4-5個區。在預設page的大小為16KB的情況下,一個區則由64個連續的page。

    InnoDB 1.2.x版本增加引數innodb_page_size引數指定page的大小,但區的大小不會改變。 當啟用了innodb_file_per_table引數後建立的表大小預設是96KB,而不是立即是1MB,是由於每個段開始先使用32個頁大小的fragment page(碎片頁)來存放資料,對於一些小表可節省磁碟空間。

 

4.3 Page (頁)

    每個page預設大小為16K, InnoDB 1.2.x版本增加引數innodb_page_size引數指定page的大小,設定完成後表中所有page大小都固定,除非重新dump再imports資料,否則不能再修改page大小。page型別有:

(1) B-tree node - 資料頁

(2) undo log page

(3) system page

(4) transaction system page
(5) insert buffer bitmap

(6) insert buffer free list

(7) uncompressed BLOB page

(8) compressed BLOB page

 

// 本文後續待繼續完善書籍中的其他重要知識點 //

&n