Innodb關鍵特性之插入緩衝

  • 2019 年 11 月 6 日
  • 筆記

Innodb關鍵特性之插入緩衝

Insert Buffer

什麼是insert buffer?

插入緩衝,也稱之為insert buffer,它是innodb存儲引擎的關鍵特性之一,我們經常會理解插入緩衝時緩衝池的一個部分,這樣的理解是片面的,insert buffer的資訊一部分在記憶體中,另外一部分像數據頁一樣,存在於物理頁中。

在innodb中,我們知道,如果一個表有自增主鍵,那麼對於這個表的默認插入是非常快的,注意,這裡的主鍵是自增的,如果不是自增的,那麼這個插入將會變成隨機的,就可能帶來數據頁分裂的開銷,這樣,插入就不是順序的,就會變慢。還有一種情況,就是如果我們插入的id不是順序的,而是隨機的,那麼即使有自增主鍵,那麼插入的速度也不會特別快。

如果我們定義了一個表,包含一個主鍵和一個非聚集索引,如下:

create table t(

a int auto_increment,

b varchar(30),

primary key(a),

key (b)

);

當我們按照主鍵a進行插入的時候,對於非聚集索引,也就是常說的二級索引b,它的插入不是順序的,插入性能必然會下降。

Innodb存儲引擎針對這種情況,設計了Insert Buffer,對於非聚集索引的插入或者更新操作,不是每一次插入到索引頁中,而是先判斷插入的非聚集索引頁是否在緩衝池中,如果在,則直接插入,如果不在,則先放入一個insert buffer中,告訴資料庫這個非聚集的索引已經插入到了葉子節點,實際上並沒有插入,只是存放在另外一個位置,然後再以一定的頻率和情況進行Insert buffer和輔助索引葉子節點合併操作。這種時候,經常能將多條記錄的插入合併到一個操作中,這樣就大大提高了非聚集索引離散插入的性能。

insert buffer的觸發條件?

insert buffer需要滿足兩個條件才能被使用,第一,索引是輔助索引,也就是二級索引,第二,索引不是唯一的。當滿足上述兩個條件的時候,就可以使用insert buffer,從而提高資料庫的插入操作性能。

這裡需要注意,如果在程式進行了大量操作的時候發生了MySQL資料庫的宕機,那麼肯定有大量的insert buffer沒有合併到實際的非聚集索引中去,恢復可能會造成很長的時間。

為什麼不能是唯一索引?

之所以不支援唯一索引,是因為如果輔助索引是唯一索引,那麼在插入時需要校驗唯一性,校驗唯一性的時候就會發生離散讀取,從而又增加了開銷,那麼insert buffer得不償失。

我們可以通過show engine innodb status來查看insert buffer的使用情況,如下:

mysql--root@localhost:dms_alimetadata ::>>show engine innodb statusG  -------------------------------------  INSERT BUFFER AND ADAPTIVE HASH INDEX  -------------------------------------  Ibuf: size , free list len , seg size ,  merges  merged operations:   insert , delete mark , delete  discarded operations:   insert , delete mark , delete  

其中size代表了已經合併記錄頁的數量,free list len代表了空閑列表的長度,seg size顯示了當前insert buffer的大小為2*16KB

引入Change Buffer的概念

最新的MySQL5.7已經支援change buffer,事實上,它在innodb 1.0.x版本已經引入,這個change buffer 可以理解為insert buffer的升級,也就是對常見的DML語言都可以進行緩衝,包含insert delete以及update,對應的分別是insert buffer,delete buffer以及purge buffer。

當然,change buffer的使用對象仍然是非唯一的輔助索引。

這裡我們以update操作為例,update的過程可以拆分為兩個部分:

第一個部分是將記錄的delete_mask標記為刪除,如果你不了解delete_mask,可以在4月9號的文章中去看。第二個部分是真正的將記錄刪除。

而delete buffer對應的是update的第一個過程,purge buffer對應的是第二個部分。

在innodb中,我們可以通過參數innodb_change_buffering來開啟buffer的各種選項,該參數可選的值為inserts,deletes,purges,changes,all,none等,其中inserts,deletes和purges就是前面討論過的情況,changes表示開啟inserts和deletes,all表示開啟所有,默認的參數如下:

mysql--root@localhost:dms_alimetadata ::>>show variables like '%buffering%';  +-------------------------+-------+  | Variable_name           | Value |  +-------------------------+-------+  | innodb_change_buffering | all   |  +-------------------------+-------+   row in set (0.01 sec)  

我們還可以通過innodb_change_buffer_max_size來控制change_buffer的最大使用記憶體數量,該參數的默認值是25,也就是1/4,示例如下:

mysql--root@localhost:dms_alimetadata ::>>show variables like '%innodb_change_buffer_max_size%';  +-------------------------------+-------+  | Variable_name                 | Value |  +-------------------------------+-------+  | innodb_change_buffer_max_size |     |  +-------------------------------+-------+   row in set (0.00 sec)  

在上面的show engine innodb status命令的輸出結果中,顯示了merged operation和discarded operation,其中insert 表示insert buffer的操作次數,delete mark表示delete buffer的操作次數,而delete表示purge buffer的操作次數,discarded operation表示當change buffer發生merge時,表已經被刪除,此時就無需進行合併。

Insert Buffer的實現?

insert buffer的數據結構是一棵B+樹,類似聚集索引一樣,全局只有一棵insert buffer B+樹,它負責對所有的表進行insert buffer,而這棵B+樹放在共享表空間中,也就是ibdata1文件中,因此,試圖通過ibd文件恢復表數據的時候可能會出現check table失敗,原因是表的輔助索引中的數據可能還在insert buffer中,所以通過ibd文件恢復文件之後,還需要進行repair table操作來重建表上的輔助索引。

insert buffer既然是一棵樹,那麼必定有葉子節點和非葉子節點,非葉子節點存放的是查詢的search key值,它的構造如下:

+---------+------------+-------+  | space   |   marker   | Value |  +---------+------------+-------+  

這個結構一共佔用9個位元組,其中,space表示待插入的記錄所在的表的表空間id,這個id是每個表都要有的唯一的id,其中space佔用4個位元組,marker佔用1個位元組,用來兼容老版本的insert buffer,offset佔用4個位元組,表示頁所在的偏移量。

輔助索引的插入過程?

當一個輔助索引要插入到數據頁的時候,如果這個數據頁不在緩衝池中,那麼innodb會根據規則構造一個search key,接下來將這個記錄插入到insert buffer的B+樹裡面去,插入的過程中,需要對這個記錄進行一些構造,最終插入的結果是類似下面這樣的一條記錄:

+---------+------------+-------+------------+------+-------+------+-------+  | space   |   marker   | Value | metadata   |      |       |      |       |  +---------+------------+-------+------------+------+-------+------+-------+  

可以發現,最後面多了一個metadata的欄位和4個其他的欄位,先來說說metadata的欄位,它佔用4個位元組,它用來排序每個記錄進入insert buffer的順序,從第5列開始,就是實際插入記錄的各個欄位的值了,因此和單純的數據記錄相比,insert buffer需要額外13個位元組的開銷。

為了保證每次merge insert buffer成功,需要設置一個特殊的數據頁來標記每個輔助索引頁的可用空間,這個數據頁的類型為insert buffer bitmap,這個頁可以追蹤很多輔助索引頁的可用空間。這裡簡單了解一下,下面會解釋它的用法。

Merged Insert Buffer的時機?

我們前面已經知道,當插入記錄的輔助索引頁不在緩衝池中的時候,需要將輔助索引記錄插入到這棵B+樹中,後續會從insert buffer中往真正的輔助索引中進行合併,那麼什麼時候進行合併呢?

1、輔助索引頁被讀取到緩衝池的時候

2、insert buffer Bitmap追蹤到該輔助索引頁已經沒有足夠的可用空間時,一般的閾值是輔助索引頁空間的1/32

3、master thread每秒執行一次merge insert buffer的操作