InnoDB儲存引擎——插入緩衝

readyao發表於2017-03-13

InnoDB儲存引擎的關鍵特性包括:

  • 插入緩衝(Insert Buffer)
  • 兩次寫(Double Write)
  • 自適應雜湊索引(Adaptive Hash Index)
  • 非同步IO(Async IO)
  • 重新整理鄰接頁(Flush Neighbor Page)

1、Insert Buffer

在InnoDB儲存引擎中,主鍵是行唯一的識別符號。通常應用程式中行記錄的插入順序是按照主鍵遞增的順序進行插入的。因此,插入聚集索引一般是順序的,不需要磁碟的隨機讀取。比如下面的SQL定義表:

create table test{
    a int auto_increment,
    b varchar(30),
    primary key(a)
);

其中a列是自增長的,若對a列插入NULL值,則由於其具有auto_increment屬性,其值會自動增長。同時頁中的行記錄按a的值進行順序存放。
在一般情況下,不需要隨機讀取另一個頁中的記錄。因此,對於這類情況下的插入操作,速度是非常快的。

一般情況下,一張表上有多個非聚集的輔助索引。如果表中有一個主鍵a,同時也有非聚集的且不是唯一的索引,那麼在進行插入操作時,資料頁的存放還是按主鍵a進行順序存放的,但是對於非聚集索引葉子節點的插入不再是順序的,由於隨機讀取的存在而導致了插入效能的下降。

InnoDB儲存引擎中的Insert Buffer,對於非聚集索引的插入或更新操作,不是每一次直接插入到索引頁中,而是先判斷插入的非聚集索引頁是否在緩衝池中,若在,則直接插入;若不在,則先放入到一個Insert Buffer物件中。然後再以一定的頻率進行Insert Buffer與輔助索引頁子節點的合併操作,這時通常能將多個插入合併到一個操作中,這就大大提高了對於非聚集索引插入的效能。

Insert Buffer的使用需要同時滿足以下兩個條件:

  • 索引是輔助索引;
  • 索引不是唯一的;

當滿足以上兩個條件時,InnoDB儲存引擎會使用Insert Buffer,這樣就提高了插入的效能。

輔助索引不能是唯一的,因為在插入緩衝時,資料庫並不去查詢索引頁來判斷插入的記錄的唯一性。如果去查詢肯定又會有離散讀取的情況發生,從而導致Insert Buffer失去了意義;

可以通過下面的命令來檢視插入緩衝的資訊:

mysql> show engine innodb status\G

下面是部分輸出:

-------------------------------------
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 276671, node heap has 0 buffer(s)
0.00 hash searches/s, 0.00 non-hash searches/s

seg size顯示了當前的Insert Buffer的大小為2*16KB,為32KB;
free list len代表了空閒列表的長度;
size代表了已經合併記錄頁的數量;

Insert Buffer目前存在的一個問題是:在寫密集的情況下,插入緩衝會佔用過多的緩衝池記憶體,預設最大可以佔用到1/2的緩衝池記憶體。
下面是InnoDB原始碼中對於insert buffer的初始化操作:

#define IBUF_POLL_SIZE_PER_MAX_SIZE 2
ibuf->max_size = buf_pool_get_curr_size()/UNIV_PAGE_SIZE/IBUF_POLL_SIZE_PER_MAX_SIZE

修改IBUF_POLL_SIZE_PER_MAX_SIZE可以對插入緩衝的大小進行控制。比如將其改為3,則最大隻能使用1/3的緩衝池記憶體;

2、Change Buffer

InnoDB從1.0.x版本開始引入了Change Buffer,可以將其視為Insert Buffer的升級。從這個版本開始,InnoDB可以對DML操作——Insert、Delete、Update都進行緩衝,它們分別是:Insert Buffer, Delete Buffer,Purge Buffer。

和之前的Insert Buffer一樣,Change Buffer適用的物件依然是非唯一的輔助索引。

對一條記錄進行Update操作可能分為兩個過程:
1)將記錄標記為已刪除;
2)真正將記錄刪除;
因此,Delete Buffer對應的Update操作的第一個過程,即將記錄標記為刪除。
Purge Buffer對應Update操作的第二個過程,即將記錄真正的刪除。
同時,InnoDB提供了引數innodb_change_buffering,用來開啟各種Buffer的選項。該引數可選的值為:inserts,deletes,purges,changes,all,none。
inserts,deletes,purges就是前面討論的三種情況。
changes表示啟動inserts和deletes,all表示啟動所有,none表示都不啟動。該引數預設值為all。

mysql> show variables like 'innodb_change_buffering';            
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| innodb_change_buffering | all   |
+-------------------------+-------+
1 row in set (0.00 sec)

從InnoDB 1.2.x版本開始,可以通過引數innodb_change_buffer_max_size來控制Change Buffer最大使用記憶體的數量,預設值為25,表示最多使用1/4的緩衝池記憶體控制元件。該引數的最大有效值為50;

mysql> show engine innodb status\G

下面是部分輸出:

-------------------------------------
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 276671, node heap has 0 buffer(s)
0.00 hash searches/s, 0.00 non-hash searches/s

這裡可以看到merged operation和discarded operation,並且下面具體顯示Change Buffer中每個操作的次數,insert表示Insert Buffer;delete mark表示Delete Buffer;delete表示Purge Buffer;
discarded operations表示當Change Buffer發生merge時,表已經被刪除,此時就無需再將記錄合併(merge)到輔助索引中了。

3、Insert Buffer的內部實現

之前瞭解了Insert Buffer是什麼,也知道了它的使用場景是針對非唯一輔助索引的插入操作。

Insert Buffer的資料結構是一棵B+樹。
在MySql 4.1之前的版本中每張表有一棵Insert Buffer B+樹。而現在的版本中,全域性只有一棵Insert Buffer B+樹,負責對所有的表的輔助索引進行Insert Buffer。

這棵B+樹存放在共享表空間中,預設是ibdata1中。因此,試圖通過獨立表空間ibd檔案恢復表中資料時,往往會導致Check Table失敗。這是因為表的輔助索引中的資料可能還在Insert Buffer中,也就是共享表空間中,所以通過ibd檔案進行恢復後,還需要進行Repair Table操作來重建表上的所有的輔助索引。

Insert Buffer是一棵B+樹,因此其也由葉節點和非葉節點組成。
非葉節點存放的是查詢的search key(鍵值),其構造如下:
這裡寫圖片描述

search key佔用9個位元組,其中space表示待插入記錄所在表的表空間id,在InnoDB儲存引擎中,每個表有一個唯一的space id,可以通過space id查詢得知是哪張表。space 佔用4位元組。marker佔1個位元組,它是用來相容老版本的Insert Buffer。offset表示頁所在的偏移量,佔用4個位元組。

當一個輔助索引要插入到頁(space,offset)時,如果這個頁不在緩衝池中,那麼InnoDB儲存引擎首先根據上述規則構造一個search key,接下來查詢Insert Buffer這棵B+樹,然後再將這條記錄插入到Insert Buffer B+樹的葉子節點中。

對於插入到Insert Buffer B+樹葉子節點的記錄,並不是直接將待插入的記錄插入,而是需要根據如下的規則進行構造:
這裡寫圖片描述

space,marker,offset和之前的意思一樣,共9個位元組。
metadata佔用4個位元組,其儲存的內容如下所示:
這裡寫圖片描述

IBUF_REC_OFFSET_COUNT是儲存兩個位元組的整數,用來排序每個記錄進入Insert Buffer的順序。

後面的就是實際插入記錄的各個欄位的內容,所以,Insert Buffer B+樹的葉子節點記錄需要額外13位元組的開銷。

啟動Insert Buffer索引後,輔助索引頁(space,offset)中的記錄可能被插入到Insert Buffer B+樹中,所以為了保證每次Merge Insert Buffer頁必須成功,還需要有一個特殊的頁用來標記每個輔助索引頁(space,offset)的可用空間。這個頁型別為Insert Buffer Bitmap;

每個Insert Buffer Bitmap頁用來追蹤16384個輔助索引頁,也就是256個區(Extent)。
每個輔助索引頁在Insert Buffer Bitmap頁中佔用4bit,各個位的含義如下:
這裡寫圖片描述

4、Merge Insert Buffer

Insert/Change Buffer是一棵B+樹。若需要實現插入記錄的輔助索引頁不在緩衝池中,那麼需要將輔助索引記錄首先插入到這棵B+樹中。但是Insert Buffer中的記錄何時合併(Merge)到真正的輔助索引中?

Merge Insert Buffer的操作可能發生在以下幾種情況:
1)輔助索引頁被讀取到緩衝池時;
2)Insert Buffer Bitmap頁追蹤到該輔助索引頁已無可用空間;
3)Master Thread;

當輔助索引頁被讀取到緩衝池中時,比如當執行select操作時,這時需要檢查Insert Buffer Bitmap頁,然後確認該輔助索引頁是否有記錄存放於Insert Buffer B+樹中。若有,則將Insert Buffer B+樹中該頁的記錄插入到該輔助索引頁中。

Insert Buffer Bitmap頁追蹤每個輔助索引頁的可用空間,並至少有1/32頁的空間。若插入輔助索引記錄時檢測到插入記錄後可用空間會小於1/32頁,則會強制進行一次合併操作,即強制讀取輔助索引頁,將Insert Buffer B+樹中該頁的記錄插入到該輔助索引頁中。

Master Thread執行緒中每秒或每10秒會進行一次Merge Insert Buffer的操作,不同之處在於每次進行merge操作的頁的數量不同。

在Master Thread中,執行merge操作的不止是一個頁,而是根據srv_innodb_io_capacity的百分比來決定真正要合併多少個輔助索引頁。但InnoDB儲存引擎又是根據什麼要合併的輔助索引頁呢?

在Insert Buffer B+樹中,輔助索引頁根據(space,offset)已經排好序了,可以根據(space,offset)的排序順序進行頁的選擇。

然而,Insert Buffer頁的選擇,InnoDB不是採用這個方式,它隨機地選擇Insert Buffer B+樹的一個頁,讀取該頁中的space及之後所需要數量的頁。
若進行merge時 ,要進行merge的表已經被刪除,此時可以直接丟棄已經被Insert/Change Buffer的資料記錄;

相關文章