1. 程式人生 > 資料庫 >Mysql-Innodb特性之插入快取

Mysql-Innodb特性之插入快取

InnoDB儲存引擎的關鍵特性包括插入緩衝、兩次寫(double write)、自適應雜湊索引(adaptive hash index)。這些特性為InnoDB儲存引擎帶來了更好的效能和更高的可靠性。

問題引入

比如說我們按下列SQL定義的表:

create table t
(id int auto_increment,
name varchar(30),
primary key(id));

id列是自增長的,這意味著當執行插入操作時,id列會自動增長,頁中的行記錄按id執行順序存放。一般情況下,不需要隨機讀取另一頁執行記錄的存放。因此,在這樣的情況下,插入操作一般很快就能完成。

但是,不可能每張表上只有一個聚集索引,在更多的情況下,一張表上有多個非聚集的輔助索引(secondary index)。比如,我們還需要按照name這個欄位進行查詢,並且name這個欄位不是唯一的。

表是按如下的SQL語句定義的:

create table t 
(id int auto_increment,
name varchar(30),
primary key(id),key(name));

這樣的情況下產生了一個非聚集的並且不是唯一的索引。在進行插入操作時,資料頁的存放還是按主鍵id的執行順序存放,但是對於非聚集索引,葉子節點的插入不再是順序的了。這時就需要離散地訪問非聚集索引頁,插入效能在這裡變低了。然而這並不是這個name欄位上索引的錯誤,因為B+樹的特性決定了非聚集索引插入的離散性。

InnoDB儲存引擎開創性地設計了插入緩衝,對於非聚集索引的插入或更新操作,不是每一次直接插入索引頁中,而是先判斷插入的非聚集索引頁是否在緩衝池中。

插入緩衝

在進行資料插入時必然會引起索引的變化,聚集索引不必說,一般都是遞增有序的。而非聚集索引就不一定是什麼資料了,其離散性導致了在插入時結構的不斷變化,從而導致插入效能降低。

所以為了解決非聚集索引插入效能的問題,InnoDB引擎 創造了Insert Buffer

插入緩衝,並不是快取的一部分,而是物理頁,對於非聚集索引的插入或更新操作,不是每一次直接插入索引頁.而是先判斷插入的非聚集索引頁是否在緩衝池中。

如果在,則直接插入,如果不在,則先放入一個插入緩衝區中.然後再以一定的頻率執行插入緩衝和非聚集索引頁子節點的合併操作.

插入緩衝的使用需要滿足以下兩個條件:

1.索引是輔助索引。

2.索引不是唯一的。

先解釋一下第一點,輔助索引指的是所有除了主鍵索引之外的其他索引。而主鍵索引的插入一般都是有序插入,如id、流水單號、交易單號等等,這種插入一般帶來的都是順序寫,io寫入效能高。而輔助索引下的寫入是有可能是順序寫,但大部分情況下是隨機寫,若每次插入資料最壞情況下都執行一次隨機寫,那將耗費比較多的時間。

第二點該索引是非唯一的,因為根據insert buffer的概念,我們的資料會先自己在快取池中先構造一個B+樹插入索引,然後在一定的時間內由master thread進行插入。若該索引必須要唯一的,那麼我們的資料在插入快取池的B+樹時候就會訪問一次磁碟,查詢當前索引是否滿足唯一性,這樣和不經過快取池直接插入到磁碟中沒有什麼分別,無法節省磁碟IO,故該索引不是唯一的。

插入緩衝是InnoDB儲存引擎關鍵特性中最令人激動的。不過,這個名字可能會讓人認為插入緩衝是緩衝池中的一個部分。其實不然,InnoDB緩衝池中有Insert Buffer資訊固然不錯,但是Insert Buffer和資料頁一樣,也是物理頁的一個組成部分。

image.png

主鍵是行唯一的識別符號,在應用程式中行記錄的插入順序是按照主鍵遞增的順序進行插入的。因此,插入聚集索引一般是順序的,不需要磁碟的隨機讀取。

當滿足以上兩個條件時,InnoDB儲存引擎會使用插入緩衝,這樣就能提高效能了。不過考慮一種情況,應用程式執行大量的插入和更新操作,這些操作都涉及了不唯一的非聚集索引,如果在這個過程中資料庫發生了宕機,這時候會有大量的插入緩衝並沒有合併到實際的非聚集索引中。如果是這樣,恢復可能需要很長的時間,極端情況下甚至需要幾個小時來執行合併恢復操作。

輔助索引不能是唯一的,因為在把它插入到插入緩衝時,我們並不去查詢索引頁的情況。如果去查詢肯定又會出現離散讀的情況,插入緩衝就失去了意義。

檢視插入緩衝的資訊:

show engine innodb status\G
Ibuf: Size 7545,free list len 3790,
seg size 11336,  
8075308 inserts,7540969 merged recs,
2246303 merges  

seg size顯示了當前插入緩衝的大小為11336*16KB,大約為177MB。free list len代表了空閒列表的長度,size代表了已經合併記錄頁的數量。

inserts代表了插入的記錄數字,merges recs代表合併的插入數,merges代表合併的次數,也就是實際讀取頁的次數。由該資料可以表明,merges:merged recs=1:3,插入快取對於非聚集索引的離散IO請求降低了2/3.

潛在問題

目前插入緩衝存在一個問題是,在寫密集的情況下,插入緩衝會佔用過多的緩衝池記憶體,預設情況下最大可以佔用1/2的緩衝池記憶體。

聚簇索引的插入

首先我們知道在InnoDB儲存引擎中,主鍵是行唯一的識別符號。我們平時插入資料一般都是按照主鍵遞增插入,因此聚集索引都是順序的,不需要磁碟的隨機讀取。

CREATE TABLE test(
 id INT AUTO_INCREMENT,
 name VARCHAR(30),
 PRIMARY KEY(id)
);

如上我建立了一個主鍵 id,它有以下的特性:

  • Id列是自增長的
  • Id列插入NULL值時,由於AUTO_INCREMENT的原因,其值會遞增,同時資料頁中的行記錄按id的值進行順序存放
  • 一般情況下由於聚集索引的有序性,不需要隨機讀取頁中的資料,因為此類的順序插入速度是非常快的。

聚簇索引的插入一定是順序的嗎?
不一定,如果你把列 Id 插入UUID這種資料,那你插入就是和非聚集索引一樣都是隨機的了。這會導致你的B+ tree結構不停地變化,那效能必然會受到影響。

非聚簇索引的插入

很多時候我們的表還會有很多非聚集索引,比如我按照b欄位查詢,且b欄位不是唯一的。如下表:

CREATE TABLE test(
 id INT AUTO_INCREMENT,
 name VARCHAR(30),
 PRIMARY KEY(id),
 KEY(name)
);

這裡我建立了一個test表,它有以下特點:

  • 有一個聚集索引 id
  • 有一個不唯一的非聚集索引 name
  • 在插入資料時資料頁是按照主鍵id進行順序存放,輔助索引 name的資料插入不是順序的
  • 非聚集索引也是一顆B+樹,只是葉子節點存的是聚集索引的主鍵和name 的值。

因為不能保證name列的資料是順序的,所以非聚集索引這棵樹的插入必然也不是順序的了,會帶來離散IO的發生。

當然如果name列插入的是時間型別資料,那其非聚集索引的插入也是順序的。

頻繁的隨機IO插入會造成效能下降

首先對於非聚集索引的插入或更新操作,不是每一次直接插入到索引頁中,而是先判斷插入的非聚集索引頁是否在緩衝池中。

若在,則直接插入;若不在,則先放入到一個Insert Buffer物件中。

給外部的感覺好像是樹已經插入非聚集的索引的葉子節點,而其實是存放在其他位置了

以一定的頻率和情況進行Insert Buffer和輔助索引頁子節點的merge(合併)操作,通常會將多個插入操作一起進行merge,這就大大的提升了非聚集索引的插入效能。

總結

其實Insert Buffer的資料結構就是一棵B+樹。

在MySQL 4.1之前的版本中每張表有一棵Insert Buffer B+樹

目前版本是全域性只有一棵Insert Buffer B+樹,負責對所有的表的輔助索引進行Insert Buffer

如果在,則直接插入;如果不在,則先放入一個插入緩衝區中,好似欺騙資料庫這個非聚集的索引已經插到葉子節點了,然後再以一定的頻率執行插入緩衝和非聚集索引頁子節點的合併操作,這時通常能將多個插入合併到一個操作中(因為在一個索引頁中),這就大大提高了對非聚集索引執行插入和修改操作的效能。

實驗表明,引入插入快取之後,整個插入效率:原始插入效率為1:3,減少了2/3的IO次數。



image.png


和Jerry哥聊技術,聊生活