InnoDB存儲引擎內部結構

InnoDB存儲引擎內部結構

從MySQL5.6開始,InnoDB是MySQL數據庫的默認存儲引擎。它支持事務,支持行鎖和外鍵,經過MVCC來得到高併發。html

MySQL5.6的InnoDB存儲引擎體系結構圖以下:node

InnoDB存儲引擎體系結構圖

此圖引用自姜承堯的博客: http://insidemysql.blog.163.com/blog/static/202834042201311104202283/mysql

InnoDB存儲引擎的內存結構大體以下圖: InnoDB內存結構git

InnoDB存儲引擎物理結構以下圖: InnoDB表空間與段區頁的關係github

InnoDB的源碼地址:https://github.com/mysql/mysql-server/tree/mysql-5.6.34/storage/innobase算法

接下來逐個介紹InnoDB存儲引擎架構的各個部分。sql

InnoDB Buffer Pool(InnoDB緩衝池)

InnoDB緩衝池是內存中用來緩存表數據和索引的一片區域。數據庫

當緩衝池大小達到GB級別時,經過設置多個緩衝池實例,能夠提升併發處理能力,減小數據庫內部資源競爭。對於存儲到或從緩衝池中讀取的每一個頁,都使用哈希函數隨機分配到不一樣的緩衝池實例中。緩存

  • innodb_buffer_pool_size: 設置緩衝池的大小(單位: Byte),重啓生效。
  • innodb_buffer_pool_instances: 設置緩衝池實例的個數,每一個緩衝池管理本身的free lists,flush lists,LRU list和其餘數據結構,並受到本身的互斥鎖保護。此值默認爲1,最大爲64。只有當 innodb_buffer_pool_size 超過1G時,此參數纔會起做用。重啓生效。
#查看buffer pool的大小(單位: Byte)
mysql> show variables like 'innodb_buffer_pool_size';

+-------------------------+-------------+
| Variable_name           | Value       |
+-------------------------+-------------+
| innodb_buffer_pool_size | 85899345920 |
+-------------------------+-------------+
1 row in set (0.01 sec)

#查看buffer pool實例個數
mysql> show variables like 'innodb_buffer_pool_instances';
+------------------------------+-------+
| Variable_name                | Value |
+------------------------------+-------+
| innodb_buffer_pool_instances | 4     |
+------------------------------+-------+
1 row in set (0.01 sec)

緩衝池的LRU算法

InnoDB以列表的方式管理緩衝池,使用優化後的LRU算法。此算法能夠最大限度地減小進入緩衝池並從未被再次訪問的頁的數量,這樣能夠確保熱點頁保持在緩衝池中。數據結構

當緩衝池的free list沒有可用的空閒頁時,InnoDB會回收LRU列表中最近最少使用的頁,並將新讀取到的頁添加到LRU列表的midpoint位置,稱之爲「midpoint insertion strategy」。它將LRU列表視爲兩個子列表,midpoint以前的列表稱爲new子列表,包含最近常常訪問的頁;midpoint以後的列表稱爲old子列表,包含最近不常訪問的頁。

最初,新添加到緩衝池的頁位於old子列表的頭部。當在緩衝池中第一次訪問這些頁時,會將它們移到new子列表的頭部,此時發生的操做稱爲page made young。隨着數據庫的運行,緩衝池中沒有被訪問到的頁因爲移到LRU列表的尾部而變老,最終會回收LRU列表尾部長時間未被訪問的頁。

能夠經過 innodb_old_blocks_pct 參數設置old子列表在LRU列表中所佔的比例。默認值爲37,對應3/8的位置。取值範圍從5到95。

爲何要將新讀取到的頁放在midpoint位置而不是LRU列表的頭部?若直接將讀取到的頁插入到LRU列表的頭部,當出現全表掃描或索引掃描的時候,須要將大量的新頁讀入到緩衝池中,致使熱點頁從緩衝池刷出,而這些新頁可能僅在此次查詢中用到,並非熱點數據,這樣就會額外產生大量的磁盤I/O操做,影響效率。爲了不此問題,InnoDB引擎引入了參數:innodb_old_blocks_time,此參數表示第一次讀取old子列表中的頁後,須要等待多少毫秒纔會將此頁移到new子列表。默認值爲1000。增長此值可讓更多的頁更快的老化。

show engine innodb status 命令輸出信息的BUFFER POOL AND MEMORY部分能夠看到LRU算法的運行狀況。詳情查看:Monitoring the Buffer Pool Using the InnoDB Standard Monitor

Change Buffer(Insert Buffer的升級)

change buffer用來緩存不在緩衝池中的輔助索引頁(非惟一索引)的變動。這些緩存的的變動,可能由INSERT、UPDATE或DELETE操做產生,當讀操做將這些變動的頁從磁盤載入緩衝池時,InnoDB引擎會將change buffer中緩存的變動跟載入的輔助索引頁合併。

不像聚簇索引,輔助索引一般不是惟一的,而且輔助索引的插入順序是相對隨機的。若不用change buffer,那麼每有一個頁產生變動,都要進行I/O操做來合併變動。使用change buffer能夠先將輔助索引頁的變動緩存起來,當這些變動的頁被其餘操做載入緩衝池時再執行merge操做,這樣能夠減小大量的隨機I/O。change buffer可能緩存了一個頁內的多條記錄的變動,這樣能夠將屢次I/O操做減小至一次。

在內存中,change buffer佔據緩衝池的一部分。在磁盤上,change buffer是系統表空間的一部分,以便數據庫重啓後緩存的索引變動能夠繼續被緩存。

innodb_change_buffering 參數能夠配置將哪些操做緩存在change buffer中。能夠經過此參數開啓或禁用insert操做,delete操做(當索引記錄初始標記爲刪除時)和purge操做(當索引記錄被物理刪除時)。update操做是inset和delete操做的組合。該參數的取值以下:

  • all: 默認值,包含inserts、deletes和purges
  • none: 不緩存任何操做
  • inserts: 緩存insert操做
  • deletes: 緩存標記刪除(delete-marking)操做
  • changes: 緩存inserts和deletes
  • purges: 緩存後臺進程發生的物理刪除操做

對應源碼以下: https://github.com/mysql/mysql-server/blob/mysql-5.6.34/storage/innobase/include/ibuf0ibuf.h

/* Possible operations buffered in the insert/whatever buffer(insert buffer中可能緩存的操做類型). See
ibuf_insert(). DO NOT CHANGE THE VALUES OF THESE, THEY ARE STORED ON DISK. */
typedef enum {
	IBUF_OP_INSERT = 0,
	IBUF_OP_DELETE_MARK = 1,
	IBUF_OP_DELETE = 2,

	/* Number of different operation types.(操做類型的數量) */
	IBUF_OP_COUNT = 3
} ibuf_op_t;

/** change buffer能夠緩存的操做的組合 */
typedef enum {
	IBUF_USE_NONE = 0,
	IBUF_USE_INSERT,	/* insert */
	IBUF_USE_DELETE_MARK,	/* delete */
	IBUF_USE_INSERT_DELETE_MARK,	/* insert+delete */
	IBUF_USE_DELETE,	/* delete+purge */
	IBUF_USE_ALL,		/* insert+delete+purge */

	IBUF_USE_COUNT		/* number of entries in ibuf_use_t */
} ibuf_use_t;

change buffer的內部實現及merge操做

Change buffer的數據結構是一顆B+樹。在MySQL4.1以前,每張表有一個insert buffer tree。自從MySQL4.1開始,全局只有一個inset buffer tree,這個B+樹在系統表空間中。數據存儲在這顆B+數的葉子節點,非葉子節點存放查詢的search key:(space_id, marker, page_no)。

每一個字段的含義:

  • space_id 表示修改記錄所屬表的表空間id,佔用4字節。
  • marker 佔用1字節,用來兼容MySQL4.1以前的insert buffer。
  • page_no 表示頁的偏移量,佔用4字節。

當一條輔助索引的記錄變動要插入到頁時,若這個也不在緩衝池中,那麼InnoDB引擎首先根據上述規則構造一個search key,而後查找在insert buffer樹,將這條記錄的變動插入到insert buffer樹的葉子節點。插入的記錄有以下部分組成:

-------------------------------------------------------------------
| space id | marker | page no | metadata |    |    |    |    |    |
-------------------------------------------------------------------
                                         |<---- 輔助索引記錄 ------>|

第4個字段metadata佔用4個字節,存儲的內容以下:

名稱 字節 說明
IBUF_REC_OFFSET_COUNTER 2 計數器,用來排序記錄,以進入insert buffer的順序
IBUF_REC_OFFSET_TYPE 1 操做類型(ibuf_op_t)
IBUF_REC_OFFSET_FLAGS 1 標誌位,當前只有IBUF_REC_COMPACT

爲了保證change buffer的merge操做必須成功,須要有一個特殊的頁用來標記每一個輔助索引頁的可用空間,這個頁的類型爲Insert buffer bitmap(簡稱ibuf bitmap)。每一個輔助索引頁在ibuf bitmap頁中佔用4位,由三部分組成,以下:

名稱 大小(Bit) 說明
IBUF_BITMAP_FREE 2 該頁中空閒頁的數量
IBUF_BITMAP_BUFFERED 1 該頁有變動緩存在change buffer
IBUF_BITMAP_IBUF 1 該頁爲change buffer的索引頁

當ibuf bitmap頁中記錄的輔助索引頁的可用空間不足時,會執行merge操做。

change buffer的merge操做在一下狀況下發生:

  • 輔助索引頁被載入到緩衝池
  • Insert Buffer Bitmap頁追蹤到該輔助索引頁已無可用空間
  • Master Thread中會每1秒或每10秒執行一次merge操做

從MySQL5.6.2開始,能夠經過 innodb_change_buffer_max_size 參數來控制change buffer最大佔用緩衝池總大小的百分比。默認值爲25,最大值爲50。

能夠經過 SHOW ENGINE INNODB STATUS 命令來查看change buffer的狀態信息:

-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
merged operations:
 insert 0, delete mark 0, delete 0
discarded operations:
 insert 0, delete mark 0, delete 0
Hash table size 4425293, used cells 32, node heap has 1 buffer(s)
13577.57 hash searches/s, 202.47 non-hash searches/s

Adaptive Hash Index(AHI,自適應哈希索引)

自適應哈希索引是InnoDB表經過在內存中構造一個哈希索引來加速查詢的優化技術,此優化只針對使用 '=' 和 'IN' 運算符的查詢。MySQL會監視InnoDB表的索引查找,若能經過構造哈希索引來提升效率,那麼InnoDB會自動爲常常訪問的輔助索引頁創建哈希索引。

這個哈希索引老是基於輔助索引(B+樹結構)來構造。MySQL經過索引鍵的任意長度的前綴和索引的訪問模式來構造哈希索引。InnoDB只爲某些熱點頁構建哈希索引。

思考:爲何只是基於輔助索引頁來構造哈希索引?

可經過 innodb_adaptive_hash_index 參數開啓或禁用此功能,默認是開啓狀態。開啓此功能後, InnoDB會根據須要自動建立這個哈希索引,而不用人爲干預建立,這就是叫自適應的緣由。此功能並非在全部狀況下都適用,且AHI須要的內存都是從緩衝池申請的,因此此功能的開啓或關閉須要經過測試來具體肯定。能夠經過 SHOW ENGINE INNODB STATUS 命令查看AHI的使用情況。

Redo Log

重作日誌用來實現事務的持久性。其由兩部分組成:一是內存中的重作日誌緩衝(redo log buffer),其是易失的;二是磁盤上的重作日誌文件(redo log file),其是持久的。

在MySQL數據庫宕機恢復期間會用到重作日誌文件,用於更正不完整事務寫入的數據。

InnoDB經過Force Log at Commit機制實現事務的持久性,即在事務提交前,先將事務的重作日誌刷新到到重作日誌文件。

重作日誌在磁盤上由一組文件組成,一般命名爲 ib_logfile0 和 ib_logfile1。

MySQL以循環方式將日誌寫入重作日誌文件。假設如今有兩個重作日誌文件:ib_logfile1和ib_logfile1。重作日誌先寫入到ib_logfile1,當ib_logfile0寫滿後再寫入ib_logfile1。當ib_logfile1也寫滿後,再往ib_logfile0中寫,而以前的內容會被覆蓋。

innodb_log_file_size 參數用來設置每一個重作日誌文件的大小(單位:Byte)。innodb_log_files_in_group 參數用來設置重作日誌文件組中日誌文件的個數。 innodb_log_group_home_dir 參數設置重作日誌文件所在的路徑。從MySQL5.6.3開始,重作日誌文件總大小的最大值從以前的4GB提高到了512GB。

mysql> show variables like 'innodb_log_%';
+-------------------------------+-----------------------+
| Variable_name                 | Value                 |
+-------------------------------+-----------------------+
| innodb_log_file_size          | 1073741824            |
| innodb_log_files_in_group     | 3                     |
| innodb_log_group_home_dir     | /opt/local/mysql/var/ |
...
+-------------------------------+-----------------------+
10 rows in set (0.01 sec)

重作日誌(redo log)跟二進制日誌(binlog)的區別:

  • 重作日誌在InnoDB存儲引擎層產生;而二進制日誌在MySQL數據庫上層產生,不只僅針對InnoDB引擎,MySQL中的任何存儲引擎對於數據庫的變動都會產生二進制日誌。
  • 二者記錄的內容形式不一樣,二進制日誌是一種邏輯日誌,其記錄的是對應的SQL語句;重作日誌是物理格式日誌,其記錄的是每一個頁的變動。
  • 兩種日誌記錄寫入磁盤的時間點不一樣,二進制日誌只在事務提交完成後進行寫入;而重作日誌在事務進行中被不斷的寫入,也就是日誌並非隨事務提交的順序寫入的。

Redo Log Buffer(重作日誌緩衝)

重作日誌緩衝是一塊內存區域,用來緩存即將被寫入到重作日誌文件的數據。InnoDB引擎首先將重作日誌信息緩存到重作日誌緩衝,而後按期將其刷新到磁盤上的重作日誌文件。

以下三種狀況會將重作日誌緩衝中的數據刷新到磁盤的重作日誌文件中:

  • Master Thread會按期將重作日誌緩衝刷新到重作日誌文件,即便這個事務尚未提交。
  • 事務提交時
  • 當重作日誌緩衝沒有足夠的空間時

InnoDB經過Force Log at Commit機制實現事務的持久性,即在事務提交前,先將事務的重作日誌刷新到到重作日誌文件。

innodb_flush_method 定義用於將數據刷新到InnoDB數據文件和日誌文件的方法,會影響I/O吞吐量。默認值爲NULL,可選項包含:fsync、O_DIRECT和其餘。若在Unix-like系統上此參數設置爲NULL,那麼默認使用fsync。

fsync: InnoDB調用系統的fsync()刷新數據文件和日誌文件。

O_DIRECT: InnoDB使用O_DIRECT方式打開數據文件,而後使用fsync()刷新數據文件和日誌文件。啓用後將繞過操做系統緩存,直接寫文件。瞭解更多能夠查看: InnoDB O_DIRECT選項漫談(一)

爲確保每次重作日誌緩衝都能寫入到磁盤的重作日誌文件,在每次將重作日誌緩衝寫入重作日誌文件後,InnoDB引擎都須要調用一次fsync操做。因爲默認狀況下 innodb_flush_method 參數未設置爲O_DIRECT,所以重作日誌緩衝先寫入文件系統緩存。

innodb_log_buffer_size 參數能夠設置重作日誌緩衝的大小。

innodb_flush_log_at_trx_commit 參數用來控制重作日誌緩衝刷新到磁盤的策略。取值範圍以下:

  • 1: 默認值。表示事務提交時必須調用一次fsync操做。
  • 0: 表示事務提交時不進行刷新日誌到磁盤。這個操做在master thread中完成,在master thread中每1秒會進行一次刷新日誌到磁盤操做。
  • 2: 表示事務提交時將重作日誌緩衝寫入重作日誌文件,但僅寫入文件系統的緩存中,不進行fsync操做。此狀況下,若操做系統發生宕機,會丟失未從文件系統緩衝刷新到重作日誌文件的那部分日誌。

Undo Log

undo log(也稱爲rollback segment)用來存儲被事務修改的記錄的副本。

undo日誌有兩個做用:一個是實現事務的原子性,即當事務因爲意外狀況未能成功運行時,可使事務回滾,從而讓數據恢復到事務開始時的狀態;另外一個做用是實現MVCC機制,當用戶讀取一行記錄時,若該記錄已經被其餘事務佔有,當前事務能夠經過undo日誌讀取該記錄以前的版本信息,以此實現一致性非鎖定讀。

每一個回滾段(rollback segment)記錄了1024個undo段(undo segment),InnoDB引擎在每一個undo段中進行undo頁的申請。

undo log分爲insert undo log和update undo log。

  • insert undo log指事務在INSERT操做中產生的undo日誌。由於INSERT操做的記錄僅對當前事務可見,因此該undo日誌在事務提交後能夠直接刪除。
  • update undo log一般保存的是對DELETE和UPDATE操做產生的undo日誌。該undo日誌可能須要提供MVCC機制,所以其不能在事務提交後當即刪除。當事務提交後,它會放入回滾段的history鏈表的頭部,等待purge線程進行最後的刪除。

默認狀況下,undo日誌位於系統表空間(system tablespace)中。從MySQL5.6起,能夠經過 innodb_undo_tablespaces 和 innodb_undo_directory 參數將undo日誌存放在獨立表空間中。詳情查看: Storing InnoDB Undo Logs in Separate Tablespaces

  • innodb_undo_directory: 用於設置undo log文件所在的路徑。
  • innodb_undo_logs: 用於設置undo log的個數,默認值爲128。
  • innodb_undo_tablespaces: 當 innodb_undo_logs 設置爲非0值時,此參數用於設置undo log分割的表空間文件的數量,文件名字爲undoN。

System Tablespace(系統表空間)

InnoDB系統表空間包含InnoDB數據字典(InnoDB相關對象的元數據)、雙寫緩衝(doublewrite buffer)、change buffer和undo logs。此外,還包含用戶在系統表空間中建立的表數據和索引數據。因爲多個表的數據能夠在共同存放在系統表空間中,以此其也稱爲共享表空間。

系統表空間可由一個或多個文件組成。默認狀況下,在MySQL數據目錄中有一個命名爲 ibdata1 的系統表空間文件。innodb_data_file_path 參數能夠設置系統表空間文件的大小和數量。具體如何設置此參數可查看: System Tablespace Data File Configuration

能夠經過 innodb_file_per_table 參數啓用獨立表空間。即每建立一個表就會產生一個單獨的 .ibd 文件存放此表的記錄和索引。若未啓用此參數,那麼InnoDB引擎建立的表就會存在於系統表空間中。

Doublewrite Buffer(雙寫緩衝)

雙寫緩衝技術是爲了解決partial page write問題而開發的。doublewrite buffer是系統表空間上的連續的128個頁(兩個區),大小爲2M。

當發生數據庫宕機時,可能InnoDB存儲引擎正在寫入某個頁到表中,而這個頁只寫了一部分,好比16KB的頁,只寫了前4KB,以後就發生了宕機,這種狀況被稱爲部分寫失效(partial page write)。

doublewrite的工做原理是:在將緩衝池中的頁寫入磁盤上對應位置以前,先將緩衝池中的頁copy到內存中的doublewrite buffer,以後再分兩次,每次1M,順序地將內存中doublewrite buffer中的頁寫入系統表空間中的doublewrite區域,讓後當即調用系統fsync函數,同步數據到磁盤文件中,避免緩衝寫帶來的問題。在完成doublewrite頁的寫入以後,再將內存上doublewrite buffer中的頁寫入到本身的表空間文件。

InnoDB存儲引擎中doublewrite的體系架構以下圖: InnoDB存儲引擎的doublewrite架構

相關閱讀:

相關文章
相關標籤/搜索