一文了解InnoDB存儲引擎

從Mysql5.5版本開始,InnoDB是默認的表存儲引擎匕垫。其特點是行鎖設(shè)計僧鲁、支持MVCC、支持外鍵象泵、提供一致性非鎖定讀寞秃、同時被設(shè)計用來最有效的利用以及使用內(nèi)存和CPU。

本文主要內(nèi)容:

  • InnoDB體系架構(gòu)
  • CheckPoint技術(shù)
  • InnoDB關(guān)鍵特性

一偶惠、InnoDB體系架構(gòu)

下圖簡單描述了InnoDB存儲引擎的體系結(jié)構(gòu):


InnoDB存儲引擎有多個內(nèi)存塊春寿,這些內(nèi)存塊組成了一個大的內(nèi)存池。后臺線程主要負責(zé)刷新內(nèi)存池中的數(shù)據(jù)忽孽、將已修改的數(shù)據(jù)刷新到磁盤等等绑改。接下來我們分別介紹后臺線程和內(nèi)存池。

1.1 后臺線程

InnoDB后臺有多個不同的線程兄一,用來負責(zé)不同的任務(wù)厘线。主要有如下:

  • Master Thread
    這是最核心的一個線程,主要負責(zé)將緩沖池中的數(shù)據(jù)異步刷新到磁盤,保證數(shù)據(jù)的一致性,包括贓頁的刷新、合并插入緩沖出革、UNDO 頁的回收等.
  • IO Thread
    在 InnoDB 存儲引擎中大量使用了異步 IO 來處理寫 IO 請求, IO Thread 的工作主要是負責(zé)這些 IO 請求的回調(diào)處理造壮。
  • Purge Thread
    事務(wù)被提交之后, undo log 可能不再需要,因此需要 Purge Thread 來回收已經(jīng)使用并分配的 undo頁. InnoDB 支持多個 Purge Thread, 這樣做可以加快 undo 頁的回收。
  • Page Cleaner Thread
    Page Cleaner Thread 是在InnoDB 1.2.x版本新引入的,其作用是將之前版本中臟頁的刷新操作都放入單獨的線程中來完成,這樣減輕了 Master Thread 的工作及對于用戶查詢線程的阻塞骂束。

1.2 內(nèi)存

這一部分费薄,我將網(wǎng)上的比較好的資料盡力去綜合,直觀的描述出來栖雾。
InnoDB 存儲引擎是基于磁盤存儲的,也就是說數(shù)據(jù)都是存儲在磁盤上的,由于 CPU 速度和磁盤速度之間的鴻溝, InnoDB 引擎使用緩沖池技術(shù)來提高數(shù)據(jù)庫的整體性能楞抡。緩沖池簡單來說就是一塊內(nèi)存區(qū)域.在數(shù)據(jù)庫中進行讀取頁的操作,首先將從磁盤讀到的頁存放在緩沖池中,下一次讀取相同的頁時,首先判斷該頁是不是在緩沖池中,若在,稱該頁在緩沖池中被命中,直接讀取該頁。否則,讀取磁盤上的頁析藕。對于數(shù)據(jù)庫中頁的修改操作,首先修改在緩沖池中頁,然后再以一定的頻率刷新到磁盤,并不是每次頁發(fā)生改變就刷新回磁盤召廷。

緩沖池中緩存的數(shù)據(jù)頁類型有:索引頁、數(shù)據(jù)頁、 undo 頁竞慢、插入緩沖先紫、自適應(yīng)哈希索引、 InnoDB 的鎖信息筹煮、數(shù)據(jù)字典信息等遮精。索引頁和數(shù)據(jù)頁占緩沖池的很大一部分(知道有這些頁,把這些頁當(dāng)做名詞即可败潦,不用感到迷惑)本冲。在InnoDB中,緩沖池中的頁大小默認為16KB劫扒。


我們已經(jīng)知道這個Buffer Pool其實是一片連續(xù)的內(nèi)存空間檬洞,那現(xiàn)在就面臨這個問題了:怎么將磁盤上的頁緩存到內(nèi)存中的Buffer Pool中呢?直接把需要緩存的頁向Buffer Pool里一個一個往里懟么沟饥?不不不添怔,為了更好的管理這些被緩存的頁,InnoDB為每一個緩存頁都創(chuàng)建了一些所謂的控制信息贤旷,這些控制信息包括該頁所屬的表空間編號广料、頁號、頁在Buffer Pool中的地址幼驶,一些鎖信息以及LSN信息(鎖和LSN這里可以先忽略)艾杏,當(dāng)然還有一些別的控制信息。

每個緩存頁對應(yīng)的控制信息占用的內(nèi)存大小是相同的县遣,我們就把每個頁對應(yīng)的控制信息占用的一塊內(nèi)存稱為一個控制塊吧,控制塊和緩存頁是一一對應(yīng)的萧求,它們都被存放到 Buffer Pool 中其兴,其中控制塊被存放到 Buffer Pool 的前邊,緩存頁被存放到 Buffer Pool 后邊夸政,所以整個Buffer Pool對應(yīng)的內(nèi)存空間看起來就是這樣的:

控制塊和緩存頁之間的那個碎片是個什么呢元旬?你想想啊,每一個控制塊都對應(yīng)一個緩存頁守问,那在分配足夠多的控制塊和緩存頁后匀归,可能剩余的那點兒空間不夠一對控制塊和緩存頁的大小,自然就用不到嘍耗帕,這個用不到的那點兒內(nèi)存空間就被稱為碎片了穆端。當(dāng)然,如果你把Buffer Pool的大小設(shè)置的剛剛好的話仿便,也可能不會產(chǎn)生碎片~

前面我們知道了緩沖池的結(jié)構(gòu)体啰。接下來說InnoDB存儲引擎是怎么對緩沖池進行管理的攒巍。講下我當(dāng)時的困惑吧,(這部分可以不看荒勇,因為有些名詞還沒涉及到)看《Mysql技術(shù)內(nèi)幕》這本書柒莉,講LRU List比較多,F(xiàn)ree List只是順帶一過沽翔,沒有將free list初始時是怎么分配的或者是什么樣的結(jié)構(gòu)兢孝,導(dǎo)致我對緩沖池的管理總是想象不出來,后來經(jīng)過在網(wǎng)上尋找資料仅偎,算是弄明白了跨蟹。來看一下吧:

當(dāng)我們最初啟動MySQL服務(wù)器的時候,需要完成對Buffer Pool的初始化過程哨颂,就是分配Buffer Pool的內(nèi)存空間喷市,把它劃分成若干對控制塊和緩存頁相种。但是此時并沒有真實的磁盤頁被緩存到Buffer Pool中(因為還沒有用到)威恼,之后隨著程序的運行,會不斷的有磁盤上的頁被緩存到Buffer Pool中寝并,那么問題來了箫措,從磁盤上讀取一個頁到Buffer Pool中的時候該放到哪個緩存頁的位置呢?或者說怎么區(qū)分Buffer Pool中哪些緩存頁是空閑的倾芝,哪些已經(jīng)被使用了呢咙冗?我們最好在某個地方記錄一下哪些頁是可用的锦担,我們可以把所有空閑的頁包裝成一個節(jié)點組成一個鏈表,這個鏈表也可以被稱作Free鏈表(或者說空閑鏈表)弦牡。因為剛剛完成初始化的Buffer Pool中所有的緩存頁都是空閑的,所以每一個緩存頁都會被加入到Free鏈表中漂羊,假設(shè)該Buffer Pool中可容納的緩存頁數(shù)量為n驾锰,那增加了Free鏈表的效果圖就是這樣的:

從圖中可以看出,我們?yōu)榱斯芾砗眠@個Free鏈表走越,特意為這個鏈表定義了一個控制信息椭豫,里邊兒包含著鏈表的頭節(jié)點地址,尾節(jié)點地址旨指,以及當(dāng)前鏈表中節(jié)點的數(shù)量等信息赏酥。我們在每個Free鏈表的節(jié)點中都記錄了某個緩存頁控制塊的地址,而每個緩存頁控制塊都記錄著對應(yīng)的緩存頁地址谆构,所以相當(dāng)于每個Free鏈表節(jié)點都對應(yīng)一個空閑的緩存頁裸扶。

有了這個Free鏈表事兒就好辦了,每當(dāng)需要從磁盤中加載一個頁到Buffer Pool中時搬素,就從Free鏈表中取一個空閑的緩存頁呵晨,并且把該緩存頁對應(yīng)的控制塊的信息填上瞬项,然后把該緩存頁對應(yīng)的Free鏈表節(jié)點從鏈表中移除,表示該緩存頁已經(jīng)被使用了~

我覺得原作者這部分講的非常好何荚,圖也很用心囱淋,直接解決了我的疑問。

不要因為走的太遠而忘記為什么出發(fā)餐塘。
簡單回顧一下妥衣,為什么講free list?是為了講怎么管理buffer pool對吧戒傻。那free list就相當(dāng)于是數(shù)據(jù)庫服務(wù)剛剛啟動沒有數(shù)據(jù)頁時税手,維護buffer pool的空閑緩存頁的數(shù)據(jù)結(jié)構(gòu)。

下面再來簡單地回顧Buffer Pool的工作機制需纳。Buffer Pool兩個最主要的功能:一個是加速讀芦倒,一個是加速寫。加速讀呢不翩? 就是當(dāng)需要訪問一個數(shù)據(jù)頁面的時候兵扬,如果這個頁面已經(jīng)在緩存池中,那么就不再需要訪問磁盤口蝠,直接從緩沖池中就能獲取這個頁面的內(nèi)容器钟。加速寫呢?就是當(dāng)需要修改一個頁面的時候妙蔗,先將這個頁面在緩沖池中進行修改傲霸,記下相關(guān)的重做日志,這個頁面的修改就算已經(jīng)完成了眉反。至于這個被修改的頁面什么時候真正刷新到磁盤昙啄,這個是后臺刷新線程來完成的。

在實現(xiàn)上面兩個功能的同時寸五,需要考慮客觀條件的限制梳凛,因為機器的內(nèi)存大小是有限的,所以MySQL的InnoDB Buffer Pool的大小同樣是有限的播歼,如果需要緩存的頁占用的內(nèi)存大小超過了Buffer Pool大小伶跷,也就是Free鏈表中已經(jīng)沒有多余的空閑緩存頁的時候豈不是很尷尬,發(fā)生了這樣的事兒該咋辦秘狞?當(dāng)然是把某些舊的緩存頁從Buffer Pool中移除叭莫,然后再把新的頁放進來嘍~ 那么問題來了,移除哪些緩存頁呢烁试?

為了回答這個問題雇初,我們還需要回到我們設(shè)立Buffer Pool的初衷,我們就是想減少和磁盤的I/O交互减响,最好每次在訪問某個頁的時候它都已經(jīng)被緩存到Buffer Pool中了靖诗。假設(shè)我們一共訪問了n次頁郭怪,那么被訪問的頁已經(jīng)在緩存中的次數(shù)除以n就是所謂的緩存命中率,我們的期望就是讓緩存命中率越高越好~

怎么提高緩存命中率呢刊橘?InnoDB Buffer Pool采用經(jīng)典的LRU算法來進行頁面淘汰鄙才,以提高緩存命中率。當(dāng)Buffer Pool中不再有空閑的緩存頁時促绵,就需要淘汰掉部分最近很少使用的緩存頁攒庵。不過,我們怎么知道哪些緩存頁最近頻繁使用败晴,哪些最近很少使用呢浓冒?呵呵,神奇的鏈表再一次派上了用場尖坤,我們可以再創(chuàng)建一個鏈表稳懒,由于這個鏈表是為了按照最近最少使用的原則去淘汰緩存頁的,所以這個鏈表可以被稱為LRU鏈表(Least Recently Used)慢味。當(dāng)我們需要訪問某個頁時场梆,可以這樣處理LRU鏈表:

  • 如果該頁不在Buffer Pool中,在把該頁從磁盤加載到Buffer Pool中的緩存頁時贮缕,就把該緩存頁包裝成節(jié)點塞到鏈表的頭部辙谜。

  • 如果該頁在Buffer Pool中俺榆,則直接把該頁對應(yīng)的LRU鏈表節(jié)點移動到鏈表的頭部感昼。

但是這樣做會有一些性能上的問題,比如你的一次全表掃描或一次邏輯備份就把熱數(shù)據(jù)給沖完了罐脊,就會導(dǎo)致導(dǎo)致緩沖池污染問題定嗓!Buffer Pool中的所有數(shù)據(jù)頁都被換了一次血,其他查詢語句在執(zhí)行時又得執(zhí)行一次從磁盤加載到Buffer Pool的操作萍桌,而這種全表掃描的語句執(zhí)行的頻率也不高宵溅,每次執(zhí)行都要把Buffer Pool中的緩存頁換一次血,這嚴重的影響到其他查詢對 Buffer Pool 的使用上炎,嚴重的降低了緩存命中率 恃逻!

所以InnoDB存儲引擎對傳統(tǒng)的LRU算法做了一些優(yōu)化,在InnoDB中加入了midpoint藕施。新讀到的頁寇损,雖然是最新訪問的頁,但并不是直接插入到LRU列表的首部裳食,而是插入LRU列表的midpoint位置矛市。這個算法稱之為midpoint insertion stategy。默認配置插入到列表長度的5/8處诲祸。midpoint由參數(shù)innodb_old_blocks_pct控制浊吏。

midpoint之前的列表稱之為new列表而昨,之后的列表稱之為old列表≌姨铮可以簡單的將new列表中的頁理解為最為活躍的熱點數(shù)據(jù)歌憨。

同時InnoDB存儲引擎還引入了innodb_old_blocks_time來表示頁讀取到mid位置之后需要等待多久才會被加入到LRU列表的熱端《昭茫可以通過設(shè)置該參數(shù)保證熱點數(shù)據(jù)不輕易被刷出躺孝。

好了,基本拿下了LRU list后底桂,我們繼續(xù)植袍。前面我們講到頁面更新是在緩存池中先進行的,那它就和磁盤上的頁不一致了籽懦,這樣的緩存頁也被稱為臟頁(英文名:dirty page)于个。所以需要考慮這些被修改的頁面什么時候刷新到磁盤?以什么樣的順序刷新到磁盤暮顺?當(dāng)然厅篓,最簡單的做法就是每發(fā)生一次修改就立即同步到磁盤上對應(yīng)的頁上,但是頻繁的往磁盤中寫數(shù)據(jù)會嚴重的影響程序的性能(畢竟磁盤慢的像烏龜一樣)捶码。所以每次修改緩存頁后羽氮,我們并不著急立即把修改同步到磁盤上,而是在未來的某個時間點進行同步惫恼,由后臺刷新線程依次刷新到磁盤档押,實現(xiàn)修改落地到磁盤。

但是如果不立即同步到磁盤的話祈纯,那之后再同步的時候我們怎么知道Buffer Pool中哪些頁是臟頁令宿,哪些頁從來沒被修改過呢?總不能把所有的緩存頁都同步到磁盤上吧腕窥,假如Buffer Pool被設(shè)置的很大粒没,比方說300G,那一次性同步這么多數(shù)據(jù)豈不是要慢死簇爆!所以癞松,我們不得不再創(chuàng)建一個存儲臟頁的鏈表,凡是在LRU鏈表中被修改過的頁都需要加入這個鏈表中入蛆,因為這個鏈表中的頁都是需要被刷新到磁盤上的响蓉,所以也叫FLUSH鏈表,有時候也會被簡寫為FLU鏈表安寺。鏈表的構(gòu)造和Free鏈表差不多厕妖,這就不贅述了。這里的臟頁修改指的此頁被加載進Buffer Pool后第一次被修改挑庶,只有第一次被修改時才需要加入FLUSH鏈表(代碼中是根據(jù)Page頭部的oldest_modification == 0來判斷是否是第一次修改)言秸,如果這個頁被再次修改就不會再放到FLUSH鏈表了软能,因為已經(jīng)存在。需要注意的是举畸,臟頁數(shù)據(jù)實際還在LRU鏈表中查排,而FLUSH鏈表中的臟頁記錄只是通過指針指向LRU鏈表中的臟頁。并且在FLUSH鏈表中的臟頁是根據(jù)oldest_lsn(這個值表示這個頁第一次被更改時的lsn號抄沮,對應(yīng)值oldest_modification跋核,每個頁頭部記錄)進行排序刷新到磁盤的,值越小表示要最先被刷新叛买,避免數(shù)據(jù)不一致砂代。

注意:臟頁既存在于LRU列表中,也存在與Flush列表中率挣。LRU列表用來管理緩沖池中頁的可用性刻伊,F(xiàn)lush列表用來管理將頁刷新回磁盤,二者互不影響椒功。

這三個重要列表(LRU list捶箱, free list,flush list)的關(guān)系可以用下圖表示:


Free鏈表跟LRU鏈表的關(guān)系是相互流通的动漾,頁在這兩個鏈表間來回置換丁屎。而FLUSH鏈表記錄了臟頁數(shù)據(jù),也是通過指針指向了LRU鏈表旱眯,所以圖中FLUSH鏈表被LRU鏈表包裹晨川。

二、CheckPoint技術(shù)

說完緩沖池键思,下面說CheckPoint技術(shù)础爬。
CheckPoint技術(shù)是用來解決如下幾個問題:

  • 縮短數(shù)據(jù)庫恢復(fù)時間
  • 緩沖池不夠用時,將臟頁刷新到磁盤
  • 重做日志不可用時吼鳞,刷新臟頁

縮短數(shù)據(jù)庫恢復(fù)時間,重做日志中記錄了的checkpoint的位置叫搁,這個點之前的頁已經(jīng)刷新回磁盤赔桌,只需要對checkpoint之后的重做日志進行恢復(fù)。這樣就大大縮短了恢復(fù)時間渴逻。

緩沖池不夠用時疾党,根據(jù)LRU算法,溢出最近最少使用的頁惨奕,如果頁為臟頁雪位,強制執(zhí)行checkpoint,將臟頁刷新回磁盤梨撞。

重做日志不可用雹洗,是指重做日志的這部分不可以被覆蓋香罐,為什么?因為:由于重做日志的設(shè)計是循環(huán)使用的时肿。這部分對應(yīng)的數(shù)據(jù)還未刷新到磁盤上庇茫。數(shù)據(jù)庫恢復(fù)時,如果不需要這部分日志螃成,即可被覆蓋旦签;如果需要,必須強制執(zhí)行checkpoint寸宏,將緩沖池中的頁至少刷新到當(dāng)前重做日志的位置宁炫。

checkpoint每次刷新多少頁到磁盤?每次從哪里取臟頁氮凝?什么時間觸發(fā)checkpoint淋淀?

InnoDB存儲引擎內(nèi)部,兩種checkpoint覆醇,分別為:

  • Sharp Checkpoint
  • Fuzzy Checkpoint

Sharp Checkpoint發(fā)生在數(shù)據(jù)庫關(guān)閉時朵纷,將所有的臟頁都刷新回磁盤,這是默認的工作方式永脓,即參數(shù):innodb_fast_shutdown=1袍辞。
不適用于數(shù)據(jù)庫運行時的刷新。

在數(shù)據(jù)庫運行時常摧,InnoDB存儲引擎內(nèi)部采用Fuzzy Checkpoint搅吁,只刷新一部分臟頁。

幾種發(fā)生Fuzzy Checkpoint的情況:
①MasterThread Checkpoint
異步刷新落午,每秒或每10秒從緩沖池臟頁列表刷新一定比例的頁回磁盤谎懦。異步刷新,即此時InnoDB存儲引擎可以進行其他操作溃斋,用戶查詢線程不會受阻界拦。

②FLUSH_LRU_LIST Checkpoint
InnoDB存儲引擎需要保證LRU列表中差不多有100個空閑頁可供使用。在InnoDB 1.1.x版本之前梗劫,用戶查詢線程會檢查LRU列表是否有足夠的空間操作享甸。如果沒有,根據(jù)LRU算法梳侨,溢出LRU列表尾端的頁蛉威,如果這些頁有臟頁,需要進行checkpoint走哺。因此叫:flush_lru_list checkpoint蚯嫌。

InnoDB 1.2.x開始,這個檢查放在了單獨的進程(Page Cleaner)中進行。好處:1.減少master Thread的壓力 2.減輕用戶線程阻塞择示。

設(shè)置參數(shù):innodb_lru_scan_dept:控制LRU列表中可用頁的數(shù)量束凑,該值默認1024

③Async/Sync Flush Checkpoint
????指重做日志不可用的情況,需要強制刷新頁回磁盤对妄,此時的頁時臟頁列表選取的湘今。
????這種情況是保證重做日志的可用性,說白了就是剪菱,重做日志中可以循環(huán)覆蓋的部分空間太少了摩瞎,換種說法,就是極短時間內(nèi)產(chǎn)生了大量的redo log孝常。
????接下來會有幾個變量旗们,圖解也不難,仔細看看构灸。
????InnoDB存儲引擎上渴,通過LSN(Log Sequence Number)來標(biāo)記版本,LSN是8字節(jié)的數(shù)字喜颁。每個頁有LSN稠氮,重做日志有LSN,checkpoint有LSN半开。
寫入日志的LSN:redo_lsn
刷新回磁盤的最新頁LSN:checkpoint_lsn
????有如下定義:
checkpoint_age = redo_lsn - checkpoint_lsn
async_water_mark = 75% * total_redo_file_size
sync_water_mark = 90% * total_redo_file_size
刷新過程如下圖所示:

④Dirty Page too much Checkpoint
即臟頁太多隔披,強制checkpoint.保證緩沖池有足夠可用的頁。
參數(shù)設(shè)置:innodb_max_dirty_pages_pct = 75 表示:當(dāng)緩沖池中臟頁的數(shù)量占75%時寂拆,強制checkpoint奢米。1.0.x之后默認75

三、InnoDB關(guān)鍵特性

3.1插入緩沖

Insert Buffer是InnoDB存儲引擎關(guān)鍵特性中最令人激動與興奮的一個功能纠永。不過這個名字可能會讓人認為插入緩沖是緩沖池中的一個組成部分鬓长。其實不然,InnoDB緩沖池中有Insert Buffer信息固然不錯尝江,但是Insert Buffer和數(shù)據(jù)頁一樣涉波,也是物理頁的一個組成部分。

一般情況下茂装,主鍵是行唯一的標(biāo)識符怠蹂。通常應(yīng)用程序中行記錄的插入順序是按照主鍵遞增的順序進行插入的。因此少态,插入聚集索引一般是順序的,不需要磁盤的隨機讀取易遣。因為彼妻,對于此類情況下的插入,速度還是非常快的侨歉。(如果主鍵類是UUID這樣的類屋摇,那么插入和輔助索引一樣,也是隨機的幽邓。)

如果索引是非聚集的且不唯一炮温。在進行插入操作時,數(shù)據(jù)的存放對于非聚集索引葉子節(jié)點的插入不是順序的牵舵,這時需要離散地訪問非聚集索引頁柒啤,由于隨機讀取的存在而導(dǎo)致了插入操作性能下降。這是因為B+樹的特性決定了非聚集索引插入的離散性畸颅。

Insert Buffer的設(shè)計担巩,對于非聚集索引的插入和更新操作,不是每一次直接插入到索引頁中没炒,而是先判斷插入非聚集索引頁是否在緩沖池中涛癌,若存在,則直接插入送火,不存在拳话,則先放入一個Insert Buffer對象中。數(shù)據(jù)庫這個非聚集的索引已經(jīng)插到葉子節(jié)點种吸,而實際并沒有弃衍,只是存放在另一個位置。然后再以一定的頻率和情況進行Insert Buffer和輔助索引頁子節(jié)點的merge(合并)操作骨稿,這時通常能將多個插入合并到一個操作中(因為在一個索引頁中)笨鸡,這就大大提高了對于非聚集索引插入的性能。

需要滿足的兩個條件:

  • 索引是輔助索引坦冠;
  • 索引不是唯一的形耗。

輔助索引不能是唯一的,因為在插入緩沖時辙浑,數(shù)據(jù)庫并不去查找索引頁來判斷插入的記錄的唯一性激涤。如果去查找肯定又會有離散讀取的情況發(fā)生,從而導(dǎo)致Insert Buffer失去了意義判呕。

3.2兩次寫

如果說插入緩沖是為了提高寫性能的話倦踢,那么兩次寫是為了提高可靠性。

介紹兩次寫之前侠草,說一下部分寫失效:
想象這么一個場景辱挥,當(dāng)數(shù)據(jù)庫正在從內(nèi)存向磁盤寫一個數(shù)據(jù)頁時,數(shù)據(jù)庫宕機边涕,從而導(dǎo)致這個頁只寫了部分數(shù)據(jù)晤碘,這就是部分寫失效褂微,它會導(dǎo)致數(shù)據(jù)丟失。這時是無法通過重做日志恢復(fù)的园爷,因為重做日志記錄的是對頁的物理修改宠蚂,如果頁本身已經(jīng)損壞,重做日志也無能為力童社。

從上面分析我們知道求厕,在部分寫失效的情況下,我們在應(yīng)用重做日志之前扰楼,需要原始頁的一個副本呀癣,兩次寫就是為了解決這個問題,下面是它的原理圖:


image.png

兩次寫需要額外添加兩個部分:
1)內(nèi)存中的兩次寫緩沖(doublewrite buffer)灭抑,大小為2MB
2)磁盤上共享表空間中連續(xù)的128頁十艾,大小也為2MB

其原理是這樣的:
1)當(dāng)刷新緩沖池臟頁時,并不直接寫到數(shù)據(jù)文件中腾节,而是先拷貝至內(nèi)存中的兩次寫緩沖區(qū)忘嫉。
2)接著從兩次寫緩沖區(qū)分兩次寫入磁盤共享表空間中,每次寫入1MB
3)待第2步完成后案腺,再將兩次寫緩沖區(qū)寫入數(shù)據(jù)文件

這樣就可以解決上文提到的部分寫失效的問題庆冕,因為在磁盤共享表空間中已有數(shù)據(jù)頁副本拷貝,如果數(shù)據(jù)庫在頁寫入數(shù)據(jù)文件的過程中宕機劈榨,在實例恢復(fù)時访递,可以從共享表空間中找到該頁副本,將其拷貝覆蓋原有的數(shù)據(jù)頁同辣,再應(yīng)用重做日志即可拷姿。

其中第2步是額外的性能開銷,但由于磁盤共享表空間是連續(xù)的旱函,因此開銷不是很大响巢。可以通過參數(shù)skip_innodb_doublewrite禁用兩次寫功能棒妨,默認是開啟的踪古,強烈建議開啟該功能。

MySQL InnoDB特性:兩次寫(DoubleWrite)
InnoDB特性之-兩次寫

3.3自適應(yīng)哈希索引

哈希是一種非橙唬快的查找方法伏穆,在一般情況時間復(fù)雜度為O(1)。而B+樹的查找次數(shù)纷纫,取決于B+樹的高度枕扫,在生成環(huán)境中,B+樹的高度一般為3-4層辱魁,不需要查詢3-4次铡原。

InnoDB存儲引擎會監(jiān)控對表上各索引頁的查詢偷厦。如果觀察到建立哈希索引可以提升速度商叹,這簡歷哈希索引燕刻,稱之為自適應(yīng)哈希索引(Adaptive Hash Index, AHI)。AHI是通過緩沖池的B+樹頁構(gòu)造而來的剖笙。因此建立的速度非陈严矗快,且不要對整張表構(gòu)建哈希索引弥咪。InnoDB存儲引擎會自動根據(jù)訪問的頻率和模式來自動的為某些熱點頁建立哈希索引过蹂。

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

  • WHERE a=XXX;

  • WHERE a=xxx AND b=xxx酷勺。
    若交替進行上述兩張查詢,InnoDB存儲引擎不會對該頁構(gòu)造AHI扳躬。此外AHI還有如下要求:

  • 以該模式訪問了100次脆诉;

  • 頁通過該模式訪問了N次,其中N=頁中記錄/16贷币。
    根據(jù)官方文檔顯示击胜,啟用AHI后,讀取和寫入的速度可以提高2倍役纹,負責(zé)索引的鏈接操作性能可以提高5倍偶摔。其設(shè)計思想是數(shù)據(jù)庫自由化的,無需DBA對數(shù)據(jù)庫進行人為調(diào)整促脉。

3.4異步IO(AIO)

為了提高磁盤操作性能辰斋,當(dāng)前的數(shù)據(jù)庫系統(tǒng)都采用異步IO的方式來處理磁盤操作。InnoDB也是如此瘸味。

與AIO對應(yīng)的是Sync IO宫仗,即每進行一次IO操作,需要等待此次操作結(jié)束才能繼續(xù)接下來的操作硫戈。但是如果用戶發(fā)出的是一條索引掃描的查詢锰什,那么這條SQL語句可能需要掃描多個索引頁,也就是需要進行多次IO操作丁逝。在每掃描一個頁并等待其完成再進行下一次掃描汁胆,這是沒有必要的。用戶可以在發(fā)出一個IO請求后立即再發(fā)出另外一個IO請求霜幼,當(dāng)全部IO請求發(fā)送完畢后嫩码,等待所有IO操作完成,這就是AIO罪既。

AIO的另外一個優(yōu)勢是進行IO Merge操作铸题,也就是將多個IO合并為一個IO操作铡恕,這樣可以提高IOPS的性能。

在InnoDB 1.1.x之前丢间,AIO的實現(xiàn)是通過InnoDB存儲引擎中的代碼來模擬的探熔。但是從這之后,提供了內(nèi)核級別的AIO的支持烘挫,稱為Native AIO诀艰。Native AIO需要操作系統(tǒng)提供支持。Windows和Linux都支持饮六,而Mac則未提供其垄。在選擇MySQL數(shù)據(jù)庫服務(wù)器的操作系統(tǒng)時,需要考慮這方面的因素卤橄。

MySQL可以通過參數(shù)innodb_use_native_aio來決定是否啟用Native AIO绿满。在InnoDB存儲引擎中,read ahead方式的讀取都是通過AIO完成窟扑,臟頁的刷新喇颁,也是通過AIO完成。

3.5刷新鄰接頁

InnoDB存儲引擎在刷新一個臟頁時辜膝,會檢測該頁所在區(qū)(extent)的所有頁无牵,如果是臟頁,那么一起刷新厂抖。這樣做的好處是通過AIO可以將多個IO寫操作合并為一個IO操作茎毁。該工作機制在傳統(tǒng)機械磁盤下有顯著優(yōu)勢。但是需要考慮下吧兩個問題:

是不是將不怎么臟的頁進行寫入忱辅,而該頁之后又會很快變成臟頁七蜘?
固態(tài)硬盤有很高IOPS,是否還需要這個特性墙懂?
為此InnoDB存儲引擎1.2.x版本開始提供參數(shù)innodb_flush_neighbors來決定是否啟用橡卤。對于傳統(tǒng)機械硬盤建議使用,而對于固態(tài)硬盤可以關(guān)閉损搬。

參考資料
《MySQL技術(shù)內(nèi)幕--InnoDB存儲引擎》第二版

彭東穩(wěn):
MySQL InnoDB Buffer Pool

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碧库,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子巧勤,更是在濱河造成了極大的恐慌嵌灰,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颅悉,死亡現(xiàn)場離奇詭異沽瞭,居然都是意外死亡,警方通過查閱死者的電腦和手機剩瓶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門驹溃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來城丧,“玉大人,你說我怎么就攤上這事豌鹤⊥龊澹” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵傍药,是天一觀的道長磺平。 經(jīng)常有香客問我,道長拐辽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任擦酌,我火速辦了婚禮俱诸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赊舶。我一直安慰自己睁搭,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布笼平。 她就那樣靜靜地躺著园骆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寓调。 梳的紋絲不亂的頭發(fā)上锌唾,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機與錄音夺英,去河邊找鬼晌涕。 笑死,一個胖子當(dāng)著我的面吹牛痛悯,可吹牛的內(nèi)容都是我干的余黎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼载萌,長吁一口氣:“原來是場噩夢啊……” “哼惧财!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起扭仁,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤垮衷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后斋枢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帘靡,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年瓤帚,在試婚紗的時候發(fā)現(xiàn)自己被綠了描姚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涩赢。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖轩勘,靈堂內(nèi)的尸體忽然破棺而出筒扒,到底是詐尸還是另有隱情,我是刑警寧澤绊寻,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布花墩,位于F島的核電站,受9級特大地震影響澄步,放射性物質(zhì)發(fā)生泄漏冰蘑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一村缸、第九天 我趴在偏房一處隱蔽的房頂上張望祠肥。 院中可真熱鬧,春花似錦梯皿、人聲如沸仇箱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剂桥。三九已至,卻和暖如春属提,著一層夾襖步出監(jiān)牢的瞬間权逗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工垒拢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留旬迹,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓求类,卻偏偏與公主長得像奔垦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子尸疆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容