InnoDB存儲引擎之InnoDB關鍵特性

1.插入緩衝
    A.Insert Buffer
        聽名字會讓人理解爲插入緩衝是緩衝池中的一部分。其實不是這個樣子的,InnoDB緩衝池中有Insert Buffer信息,但是Insert Buffer和數據頁一樣,也是物理頁的一個組成部分。在InnoDB存儲引擎中,行記錄的插入順序是按照主鍵遞增的順序進行插入的。因此插入聚集索引(Primary Key)一般是順序的,不需要磁盤的隨機讀取。但是並不是所有的主鍵都是順序的。如主鍵是UUID這類的,那麼插入和輔助索引一樣都是隨機的。所以在建表時主鍵是關鍵一般都是自增ID且非空。
        對於非聚集索引的插入或者更新操作,不是每一次直接插入到索引頁中,而是先判定插入的非聚集索引頁是否在緩衝池中,若在則直接插入;如不在則先放入到Inset Buffer中。然後再以一定的頻率和情況進行Insert Buffer和輔助索引頁子節點的merge操作。這時通常能將多少插入合併到一個操作中(因爲在一個索引頁中),這就大大提高了對於非聚集索引的性能。但是Inset Buffer的使用需要同時滿足一下兩個條件:1.索引是輔助索引;2.索引不是唯一的。如果是唯一索引的話,數據庫會去查找索引頁來判斷插入記錄的唯一性,這個樣子又會有離散讀取的情況發生,從而導致Insert Buffer失去意義。可以通過命令show engine innodb status來查看插入緩衝的信息。但是在寫密集的情況下,插入緩衝會佔用過多的緩衝池,默認最大可以佔到這個緩衝池的1/2。這對於其他的操作可能會帶來一定的影響。Percona發佈一些patch來修正這個情況。可以通過ibuf_pool_size_per_max_size參數來設置。具體的可以到官網進行查找。
    B.Change Buffer

        InnoDB從1.0.x版本開始引入了Change Buffer。對DML操作-insert、delete、update都進行緩衝。分別是:Insert Buffer、Delete Buffer、Purge Buffer。Change Buffer使用的對象依然是非唯一的輔助索引。對一條記錄進行update操作可能分爲兩個過程:1.將記錄標記未已刪除;2.真正將記錄刪除。因此delete Buffer對呀update操作的第一個過程,Purge Buffer對應update操作的第二個過程。可以通過參數innodb_change_buffering來開啓各種Buffer的選項。該參數的可選值有:inserts、deletes、purges、all、none。changes表示啓用inserts和deletes,all表示啓用所有,none表示都不啓用。默認all。在InnoDB 1.2.x還可以通過參數innodb_change_buffer_max_size(百分比)來控制最大使用的內存數量。如圖


        有圖可以看到這裏顯示了merged Operations和discarded operations。並且下邊都具體顯示Change Buffer中每個操作的次數。insert表示Insert Buffer;delete mark表示Delete Buffer;delete表示 Purge Buffer;discarded Operations表示當Change Buffer發生merge時,表已經被刪除,此時就無需將記錄合併到輔助索引中。
    C.Insert Buffer的內部實現
        在Mysql 4.1之前的版本中每張表都有一棵insert buffer B+樹。而現在的版本中只有一棵全局的insert buffer B+樹,負責對所有的表的非唯一輔助索引進行Insert Buffer。而這棵B+樹放在共享表空間中。因此,試圖通過獨立表空間ibd文件恢復表中的數據時,往往會導致check table失敗。這是因爲表的輔助索引中的數據可能還在Insert Buffer中,所以通過ibd文件恢復後,還需要通過repair table來重建表中的輔助索引。

        Insert Buffer是一棵B+樹,因此也由葉節點和非葉節點組成,非葉節點存放的是查詢額search key(鍵值),具體構造如下圖:


        search key共佔用9個字節,其中space(佔用4個字節)表示待插入記錄所在表的表空間id(在InnoDB存儲引擎中,每個表都有一個唯一的space id,可以通過space id查詢得到是那張表)。marker佔用1字節,用來兼容老版本的Insert Buffer。offset表示頁所在的偏移量,佔4字節。

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


        space、marker、offset字段的含義和非葉節點的含義相同。metadata佔用4字節,其存儲的內容如下:


        IBUF_REC_OFFSET_COUNT保存2字節的整數,用來排序每個記錄進入Insert Buffer的順序。從Insert Buffer葉節點的第5列開始,就是實際插入記錄的各個字段啦。因此較之原插入記錄,Insert Buffer B+樹需要額外13字節的開銷。

        因爲啓用Insert Buffer索引後,輔助索引頁(space, page_no)中的記錄可能被插入到Insert Buffer B+樹中,所以爲了保證每次Merge Insert Buffer頁必須成功,還需要有一個特殊的頁用來標記每個輔助索引頁(space,page_no)的可用空間。這個頁的類型稱之爲Insert Buffer Bitmap。每個Insert Buffer Bitmap頁用來追蹤16384(256個區(Extent))個輔助索引頁,每個Insert Buffer Bitmap頁都在16384個頁的第二個頁中。每個輔助索引頁在Insert Buffer Bitmap頁中佔用4位(bit),具體結構如下:


    D.Merge Insert Buffer
        概括地說,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操作頁的數量不一樣。每次Merge操作的不止一個頁,而是根據srv_innodb_io_capactiy的百分比來決定真正要合併多少個輔助索引頁。在Insert Buffer B+樹中,輔助索引頁根據(space, offset)都已排序好,故可以根據(space, offset)的排序順序進行頁的選擇。然而,對於Insert Buffer頁的選擇,InnoDB存儲引擎並非採用這個方式,它隨機地選擇Insert Buffer B+樹的一個頁,讀取該頁中的space及以後所需要數量的頁。若進行merge時,要進行merge操作的表已經被刪除,此時可以直接丟棄已經被Insert/Change Buffer的數據記錄。

2.兩次寫

    Insert Buffer使InnoDB存儲引擎的性能提升,而doublewrite(兩次寫)帶給InnoDB存儲引擎的數據頁的可靠性。這是因爲,當數據庫宕機是,InnoDB存儲引擎可能正在寫入某個頁到表中,而這個時候只寫了一部分(如16K的頁,只寫了前4K),這情況被稱爲部分寫失效(partial page write)。可能你會想着用重做日誌進行恢復。這是一個辦法。但是重做日誌記錄的是對頁的物理操作,如偏移量800,寫'aaaa'記錄。如果這個頁本身已經發生啦損壞,在對其進行重做是沒有意思的。這就是在應用重做日誌前,需要一個頁的副本,當寫入失效時,先通過頁的副本來還原該頁,再進行重做。這就是doublewrite。如下圖


    doublewrite由兩部分組成,一部分是內存中的doublewrite buffer,大小爲2M,另一部分爲物理磁盤上共享表空間中連續的128個頁(即2個區(extent))大小也是2M。在對緩衝池中的髒頁進行刷新是,並不是直接寫入磁盤,而是通過memcpy函數將髒頁複製到內存中的doublewrite buffer,之後通過doublewrite buffer分兩次,每次1M順序的寫入共享表空間的物理磁盤上,然後馬上調用fsync函數,同步磁盤,避免緩衝寫帶來的問題。在完成doublewrite頁的寫入後,在將doublewrite buffer中的頁寫入各個表空間文件中,這個時候的寫入是離散的。可以通過命令show global status like '%innodb_dblwr%';如圖


    可以看到doublewrite一共寫了1413988個頁,但實際寫入次數爲111623。如果innodb_dblwr_pages_written:innodb_dblwr_writes小於64:1,說明系統寫入壓力並不是很高。參數innodb_buffer_pool_pages_flushed表示當前從緩衝池中刷新到磁盤頁的數量。從上邊介紹的可以知道,在生產環境中如果需要統計數據的寫入量,最安全的方法還是應該通過innodb_dblwr_pages_written參數進行通過。可以通過參數innodb_doublewrite來設置設置是否開啓doublewrite功能。skip_innodb_doublewrite也可以禁止使用doublewrite功能。
    注意:有些文件系統本身就提供了部分寫失效的防範機制,如ZFS文件系統。在這種情況下,就可以不用啓用doublewrite。
3.自適應哈希索引
    哈希是一種非常快的查找方法,在一般情況時間複雜度爲O(1)。而B+樹的查找次數,取決於B+樹的高度,在生成環境中,B+樹的高度一般爲3-4層,不需要查詢3-4次。InnoDB存儲引擎會監控對錶上各索引頁的查詢。如果觀察到簡歷哈希索引可以提升速度,這簡歷哈希索引,稱之爲自適應哈希索引(Adaptive Hash Index, AHI)。AHI是通過緩衝池的B+樹頁構造而來的。因此建立的速度非常快,且不要對整張表構建哈希索引。InnoDB存儲喲inquiry會自動根據房屋的頻率和陌生來自動的爲某些熱點頁建立哈希索引。

    AHI有一個要求,對這個頁的連續訪問模式(查詢條件)必須一樣的。例如聯合索引(a,b)其訪問模式可以有以下情況:1.WHERE a=XXX;2.WHERE a=xxx AND b=xxx。若交替進行上述兩張查詢,InnoDB存儲引擎不會對該頁構造AHI。此外AHI還有如下要求:a.以該模式訪問了100次;b.頁通過該模式訪問了N次,其中N=頁中記錄/16。根據官方文檔顯示,啓用AHI後,讀取和寫入的速度可以提高2倍,負責索引的鏈接操作性能可以提高5倍。其設計思想是數據庫自由化的,無需DBA對數據庫進行人爲調整。


    由上圖可以看到AHI的使用信息,包括AHI的大小、使用情況、每秒使用AHI搜索的情況。哈希索引只能用來查詢等值的情況,而對於其他類型是不能使用哈希索引的。因此這裏出現non-hash searches/s。可以通過參數innodb_adaptive_hash_index來決定是否開啓。
4.異步IO
    爲了提高磁盤操作性能,當前的數據庫系統都採用異步IO(AIO)。在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操作。該工作機制在傳統機械磁盤下有顯著優勢。但是需要考慮下吧兩個問題:a.是不是將不怎麼髒的頁進行寫入,而該頁之後又會很快變成髒頁?b.固態硬盤有很高IOPS,是否還需要這個特性?爲此InnoDB存儲引擎1.2.x版本開始提供參數innodb_flush_neighbors來決定是否啓用。對於傳統機械硬盤建議使用,而對於固態硬盤可以關閉。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章