InnoDB關鍵特性

主要包括:

插入緩存(insert buffer)、兩次寫(double write)、自適應哈希(Adaptive Hash index)、異步IO(Async IO)、刷新鄰接頁(Flush Neighbor Page)

1、插入緩存

1.1 Insert Buffer

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

一般情況下,主鍵是行唯一的標識符。通常應用程序中行記錄的插入順序是按照主鍵遞增的順序進行插入的。因此,插入聚集索引一般是順序的,不需要磁盤的隨機讀取。因爲,對於此類情況下的插入,速度還是非常快的。(如果主鍵類是UUID這樣的類,那麼插入和輔助索引一樣,也是隨機的。)

如果索引是非聚集的且不唯一。在進行插入操作時,數據的存放對於非聚集索引葉子節點的插入不是順序的,這時需要離散地訪問非聚集索引頁,由於隨機讀取的存在而導致了插入操作性能下降。這是因爲B+樹的特性決定了非聚集索引插入的離散性。

Insert Buffer的設計,對於非聚集索引的插入和更新操作,不是每一次直接插入到索引頁中,而是先判斷插入非聚集索引頁是否在緩衝池中,若存在,則直接插入,不存在,則先放入一個Insert Buffer對象中。數據庫這個非聚集的索引已經插到葉子節點,而實際並沒有,只是存放在另一個位置。然後再以一定的頻率和情況進行Insert Buffer和輔助索引頁子節點的merge(合併)操作,這時通常能將多個插入合併到一個操作中(因爲在一個索引頁中),這就大大提高了對於非聚集索引插入的性能。

需要滿足的兩個條件:

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

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

1.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操作可能分爲兩個過程:

  • 將記錄標記爲已刪除(Delete Buffer);
  • 真正將記錄刪除(Purge buffer)。

因此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。

從InnoDB 1.2.x版本開始,可以通過參數innodb_change_buffer_max_size來控制Change Buffer最大使用內存的數量,innodb_change_buffer_max_size值默認爲25,表示最多使用1/4的緩衝池內存空間。而需要注意的是,該參數的最大有效值爲50。

在MySQL 5.5版本中通過命令SHOW ENGINE INNODB STATUS,可以觀察到類似如下的內容:

mysql> SHOW ENGINE INNODB STATUS\G;
……
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 34397, seg size 34399, 10875 merges

merged operations:

insert 20462, delete mark 20158, delete 4215

discarded operations:

insert 0, delete mark 0, delete 0
……

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

1.3 Merge Insert Buffer

通過前面的小節讀者應該已經知道了Insert/Change Buffer是一棵B+樹。若需要實現插入記錄的輔助索引頁不在緩衝池中,那麼需要將輔助索引記錄插入到這棵實際B+樹中。但是Insert Buffer中的記錄何時合併(merge)到真正的輔助索引中呢?

概括地說,Merge Insert Buffer的操作可能發生在以下幾種情況下:

  • 輔助索引頁被讀取到緩衝池時;
  • Insert Buffer Bitmap頁追蹤到該輔助索引頁已無可用空間時;
  • Master Thread。

第一種情況爲當輔助索引頁被讀取到緩衝池中時,例如這在執行正常的SELECT查詢操作,這時需要檢查Insert Buffer Bitmap頁,然後確認該輔助索引頁是否有記錄存放於Insert Buffer B+樹中。若有,則將Insert Buffer B+樹中該頁的記錄插入到該輔助索引頁中。可以看到對該頁多次的記錄操作通過一次操作合併到了原有的輔助索引頁中,因此性能會有大幅提高。

Insert Buffer Bitmap頁用來追蹤每個輔助索引頁的可用空間,並至少有1/32頁的空間。若插入輔助索引記錄時檢測到插入記錄後可用空間會小於1/32頁,則會強制進行一個合併操作,即強制讀取輔助索引頁,將Insert Buffer B+樹中該頁的記錄及待插入的記錄插入到輔助索引頁中。這就是上述所說的第二種情況。

還有一種情況,之前在分析Master Thread時曾講到,在Master Thread線程中每秒或每10秒會進行一次Merge Insert Buffer的操作,不同之處在於每次進行merge操作的頁的數量不同。

2 兩次寫

2.1 doublewrite應用場景

我們知道,innodb的數據頁一般大小是16KB,MySQL存取數據的最小單位也是頁,而操作系統並不能保障一個數據頁的原子性,也就是說當寫入數據時,有可能在一個頁中寫入一半時(比如8K)數據庫宕機,這種情況稱爲部分寫失效(partial page write),從而導致數據丟失。

大家也許會問,難道我不可以根據redo log進行數據恢復嗎?答案是肯定的也是否定的,要分爲兩種情況:1、數據庫宕機,物理文件完好無損,是可以通過redo log進行崩潰恢復。2、數據庫宕機,正在刷新到磁盤的頁發生partial page write,而正好在磁盤上的這個數據頁由於宕機發生損壞,這時就無法通過redo log進行數據恢復了,爲什麼?我們必須要清楚的認識到,redo log裏記錄的是對頁的物理操作!比如一條redo記錄"page number xx,偏移量 800 寫記錄 “this is abc”",那當頁損壞時,這條redo記錄還有意義嗎?於是在這種特殊情況下,doublewrite就派上用場啦!

2.2 doublewrite體系結構及工作流程

doublewrite由兩部分組成,一部分爲內存中的doublewrite buffer,其大小爲2MB,另一部分是磁盤上共享表空間(ibdata x)中連續的128個頁,即2個區(extent),大小也是2M。doublewrite工作流程如下:

  1. 當一系列機制(main函數觸發、checkpoint等)觸發數據緩衝池中的髒頁進行刷新時,並不直接寫磁盤,而是會通過memcpy函數將髒頁先複製到內存中的doublewrite buffer,之後通過doublewrite buffer再分兩次、每次1MB順序寫入共享表空間的物理磁盤上。
  2. 馬上調用fsync函數,同步髒頁進磁盤。由於在這個過程中,doublewrite頁的存儲時連續的,因此寫入磁盤爲順序寫,性能很高;完成doublewrite後,再將髒頁寫入實際的各個表空間文件,這時寫入就是離散的了。各模塊協作情況如下圖(第一步應爲髒頁產生的redo記錄logbuffer,然後logbuffer寫入redo log file,爲簡化次要步驟直接連線表示):

查看doublewrite工作情況,可以執行命令:

mysql> show global status like 'innodb_dblwr%'\G
*************************** 1. row ***************************
Variable_name: Innodb_dblwr_pages_written
        Value: 61932183
*************************** 2. row ***************************
Variable_name: Innodb_dblwr_writes
        Value: 15237891
2 rows in set (0.00 sec)

以上數據顯示,doublewrite一共寫了 61932183個頁,一共寫了15237891次,從這組數據我們可以分析,之前講過在開啓doublewrite後,每次髒頁刷新必須要先寫doublewrite,而doublewrite存在於磁盤上的是兩個連續的區,每個區由連續的頁組成,一般情況下一個區最多有64個頁,所以一次IO寫入應該可以最多寫64個頁。而根據以上我這個系統Innodb_dblwr_pages_written與Innodb_dblwr_writes的比例來看,大概在4左右,遠遠還沒到64,所以從這個角度也可以看出,系統寫入壓力並不高。

2.3 崩潰恢復

如果操作系統在將頁寫入磁盤的過程中發生崩潰,如上圖,在恢復過程中,innodb存儲引擎可以從共享表空間的doublewrite中找到該頁的一個最近的副本,將其複製到表空間文件,再應用redo log,就完成了恢復過程。因爲有副本所以也不擔心表空間中數據頁是否損壞。

3 自適應哈希索引

哈希是一種非常快的查找方法,在一般情況時間複雜度爲O(1)。而B+樹的查找次數,取決於B+樹的高度,在生成環境中,B+樹的高度一般爲3-4層,不需要查詢3-4次。

InnoDB存儲引擎會監控對錶上各索引頁的查詢。如果觀察到簡歷哈希索引可以提升速度,這簡歷哈希索引,稱之爲自適應哈希索引(Adaptive Hash Index, AHI)。AHI是通過緩衝池的B+樹頁構造而來的。因此建立的速度非常快,且不要對整張表構建哈希索引。InnoDB存儲喲inquiry會自動根據房屋的頻率和陌生來自動的爲某些熱點頁建立哈希索引。

AHI有一個要求,對這個頁的連續訪問模式(查詢條件)必須一樣的。例如聯合索引(a,b)其訪問模式可以有以下情況:

  • WHERE a=XXX;
  • 2.WHERE a=xxx AND b=xxx。

若交替進行上述兩張查詢,InnoDB存儲引擎不會對該頁構造AHI。此外AHI還有如下要求:

  • 以該模式訪問了100次;
  • b.頁通過該模式訪問了N次,其中N=頁中記錄/16。

根據官方文檔顯示,啓用AHI後,讀取和寫入的速度可以提高2倍,負責索引的鏈接操作性能可以提高5倍。其設計思想是數據庫自由化的,無需DBA對數據庫進行人爲調整。

4 異步IO

爲了提高磁盤操作性能,當前的數據庫系統都採用異步IO的方式來處理磁盤操作。InnoDB也是如此。

與AIO對應的是Sync IO,即每進行一次IO操作,需要等待此次操作結束才能繼續接下來的操作。但是如果用戶發出的是一條索引掃描的查詢,那麼這條SQL語句可能需要掃描多個索引頁,也就是需要進行多次IO操作。在每掃描一個頁並等待其完成再進行下一次掃描,這是沒有必要的。用戶可以在發出一個IO請求後立即再發出另外一個IO請求,當全部IO請求發送完畢後,等待所有IO操作完成,這就是AIO。

AIO的另外一個優勢是進行IO Merge操作,也就是將多個IO合併爲一個IO操作,這樣可以提高IOPS的性能。

在InnoDB 1.1.x之前,AIO的實現是通過InnoDB存儲引擎中的代碼來模擬的。但是從這之後,提供了內核級別的AIO的支持,稱爲Native AIO。Native AIO需要操作系統提供支持。Windows和Linux都支持,而Mac則未提供。在選擇MySQL數據庫服務器的操作系統時,需要考慮這方面的因素。

MySQL可以通過參數innodb_use_native_aio來決定是否啓用Native AIO。在InnoDB存儲引擎中,read ahead方式的讀取都是通過AIO完成,髒頁的刷新,也是通過AIO完成。

5 刷新鄰接頁

InnoDB存儲引擎在刷新一個髒頁時,會檢測該頁所在區(extent)的所有頁,如果是髒頁,那麼一起刷新。這樣做的好處是通過AIO可以將多個IO寫操作合併爲一個IO操作。該工作機制在傳統機械磁盤下有顯著優勢。但是需要考慮下吧兩個問題:

  • 是不是將不怎麼髒的頁進行寫入,而該頁之後又會很快變成髒頁?
  • 固態硬盤有很高IOPS,是否還需要這個特性?

爲此InnoDB存儲引擎1.2.x版本開始提供參數innodb_flush_neighbors來決定是否啓用。對於傳統機械硬盤建議使用,而對於固態硬盤可以關閉。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章