Mysql快速學(xué)習(xí)——《二》: Mysql的InnoDB存儲引擎設(shè)計

上篇文章介紹了一條SQL語句在數(shù)據(jù)庫中的執(zhí)行流程,當(dāng)SQL執(zhí)行到存儲引擎這里,因為不同的引擎的實現(xiàn)機制有所不同,現(xiàn)在就以使用最廣泛的為InnoDB引擎再來細化說明Innodb在數(shù)據(jù)查詢和更新流程的細節(jié)。

上篇: Mysql快速學(xué)習(xí)——《一》: Mysql的基礎(chǔ)架構(gòu)
參考: The InnoDB Storage Engine
思維導(dǎo)圖: mysql

存儲引擎層

存儲引擎層(Storage Engines),它決定了 MySQL 會怎樣存儲數(shù)據(jù),怎樣讀取和寫入數(shù)據(jù),也在很大程度上決定了 MySQL 的讀寫性能和數(shù)據(jù)可靠性。
對于這么重要的一層能力,MySQL 提供了極強的擴展性,你可以定義自己要使用什么樣的存儲引擎:InnoDBMyISAMMEMORYCSV,甚至可以自己開發(fā)一個存儲引擎然后使用它。

InnoDB存儲引擎

下面是InnoDB 引擎的邏輯架構(gòu)圖, 弄懂它那你對MYSQL的理解將更進一步:


innoDB架構(gòu)圖

從上圖可以看到InnoDB分為2大塊:

  • In-Memory Structures (內(nèi)存部分)
  • On-Disk Structures (磁盤部分)

其中In-Memory Structures (內(nèi)存部分)包括:

  • Buffer Pool - 內(nèi)存緩沖池
  • Change Buffer - 寫交換緩沖池
  • Adaptive Hash Index - 自適應(yīng)哈希索引
  • Log Buffer

On-Disk Structures (磁盤部分), 從架構(gòu)圖可以看到, Tablespaces 分為五種(我們平時創(chuàng)建的表的數(shù)據(jù),可以存放到 The System Tablespace 、File-Per-Table Tablespaces、General Tablespace 三者中的任意一個地方,具體取決于你的配置和創(chuàng)建表時的 sql 語句)

  • The System Tablespace - 系統(tǒng)表空間
    ---- 在數(shù)據(jù)庫建立的時候自動創(chuàng)建的,它包含了整個數(shù)據(jù)庫的數(shù)據(jù)字典。
  • File-Per-Table Tablespaces - 獨立表空間
    ---- 是對The System Tablespace(系統(tǒng)表空間)的一個更靈活的選擇, 在MySQL 5.6.6和更高版本默認啟用的。
  • General Tablespace - 通用表空間
    --- 類似于系統(tǒng)表空間,常規(guī)表空間是共享表空間,可以存儲多個表的數(shù)據(jù)。
  • Undo Tablespaces - 回退表空間
  • Temporary Tablespaces

On-Disk Structures (磁盤部分)除了表結(jié)構(gòu)定義和索引,還有一些為了高性能和高可靠而設(shè)計的角色,比如

  • redo log
  • undo log
  • Change Buffer
  • Doublewrite Buffer

內(nèi)存部分組件詳解

1. Buffer Pool

緩存表數(shù)據(jù)與索引數(shù)據(jù),把磁盤上的數(shù)據(jù)加載到緩沖池,避免每次訪問都進行磁盤IO,起到加速訪問的作用。


InnoDB緩沖池策略
  • 按頁(4K)讀取
    磁盤讀寫,并不是按需讀取,而是按頁讀取,一次至少讀一頁數(shù)據(jù)(一般是4K),如果未來要讀取的數(shù)據(jù)就在頁中,就能夠省去后續(xù)的磁盤IO,提高效率。
  • “集中讀寫”的原則(預(yù)讀)
    數(shù)據(jù)訪問,通常都遵循“集中讀寫”的原則,使用一些數(shù)據(jù),大概率會使用附近的數(shù)據(jù),這就是所謂的“局部性原理”。InnoDB會把一些“可能要訪問”的頁提前加入緩沖池,避免未來的磁盤IO操作。

傳統(tǒng)LRU緩沖池算法

為了減少數(shù)據(jù)移動,LRU一般用鏈表實現(xiàn)。最常見的玩法是,把入緩沖池的頁放到LRU的頭部,作為最近訪問的元素,從而最晚被淘汰。這里又分兩種情況:
(1)頁已經(jīng)在緩沖池里,那就只做“移至”LRU頭部的動作,而沒有頁被淘汰;
(2)頁不在緩沖池里,除了做“放入”LRU頭部的動作,還要做“淘汰”LRU尾部頁的動作;

InnoDB并不直接使用傳統(tǒng)的LRU緩沖池算法, 因為傳統(tǒng)的LRU緩沖池算法會出現(xiàn)以下問題:
(1)預(yù)讀失效: 由于預(yù)讀(Read-Ahead),提前把頁放入了緩沖池,但最終MySQL并沒有從頁中讀取數(shù)據(jù),稱為預(yù)讀失效。
(2)緩沖池污染: 當(dāng)某一個SQL語句,要批量掃描大量數(shù)據(jù)時,可能導(dǎo)致把緩沖池的所有頁都替換出去,導(dǎo)致大量熱數(shù)據(jù)被換出,MySQL性能急劇下降,這種情況叫緩沖池污染。


InnoDB對傳統(tǒng)LRU旳優(yōu)化
  • 預(yù)讀失敗優(yōu)化 - 新老生代機制
    (1)將LRU分為兩個部分:新生代(new sublist) + 老生代(old sublist)
    (2)新老生代收尾相連,即:新生代的尾(tail)連接著老生代的頭(head);
    (3)新頁(例如被預(yù)讀的頁)加入緩沖池時,只加入到老生代頭部
    (4)如果數(shù)據(jù)真正被讀取(預(yù)讀成功),才會加入到新生代的頭部
    (5)如果數(shù)據(jù)沒有被讀取,則會比新生代里的“熱數(shù)據(jù)頁”更早被淘汰出緩沖池


    新老生代機制
  • 緩沖池污染優(yōu)化 - 老生代停留時間窗口機制
    (1)假設(shè)T=老生代停留時間窗口;
    (2)插入老生代頭部的頁,即使立刻被訪問,并不會立刻放入新生代頭部;
    (3)只有滿足“被訪問”并且“在老生代停留時間”大于T,才會被放入新生代頭部;


    老生代停留時間窗口機制

buffer_pool相關(guān)重要參數(shù)
  • innodb_buffer_pool_size
    配置緩沖池的大小,在內(nèi)存允許的情況下,DBA往往會建議調(diào)大這個參數(shù),越多數(shù)據(jù)和索引放到內(nèi)存里,數(shù)據(jù)庫的性能會越好。
  • innodb_old_blocks_pct
    老生代占整個LRU鏈長度的比例,默認是37,即整個LRU中新生代與老生代長度比例是63:37。
  • innodb_old_blocks_time
    老生代停留時間窗口,單位是毫秒,默認是1000,即同時滿足“被訪問”與“在老生代停留時間超過1秒”兩個條件,才會被插入到新生代頭部。

2. Change Buffer

寫請求的處理流程

(1)如果索引頁不在buffer pool中, 則先把索引頁,從磁盤加載到緩沖池,一次磁盤隨機讀操作;
(2)修改緩沖池中的頁,一次內(nèi)存操作;
(3)寫入redo log,一次磁盤順序?qū)懖僮鳎?/p>

寫請求處理

是否會出現(xiàn)一致性問題呢?

不會, 因為:
(1)讀取,會命中緩沖池的頁;
(2)緩沖池LRU數(shù)據(jù)淘汰,會將“臟頁”刷回磁盤;
(3)數(shù)據(jù)庫異常奔潰,能夠從redo log中恢復(fù)數(shù)據(jù);


利用Change Buffer進行優(yōu)化

上述場景中, 被讀取的數(shù)據(jù)沒有命中緩沖池的時候,會先從磁盤索引頁到緩沖池中, 這樣至少產(chǎn)生一次磁盤IO,對于寫多讀少的業(yè)務(wù)場景,性能壓力會劇增, 于是InnoDB引入了Change Buffer:

  • 當(dāng)對頁進行了寫操作,并不會立刻將磁盤頁加載到緩沖池
  • 先把頁的寫操作記錄到緩沖變更池(buffer changes)
  • 等未來數(shù)據(jù)被讀取時,再將數(shù)據(jù)合并(merge)恢復(fù)到緩沖池中

寫緩沖的目的是降低寫操作的磁盤IO,提升數(shù)據(jù)庫性能。


Change Buffer

在內(nèi)存中,Change Buffer占用Buffer Pool的一部分。在磁盤上,Change Buffer是系統(tǒng)表空間的一部分,其中的索引會在關(guān)閉數(shù)據(jù)庫服務(wù)器時更改。


Change Buffer相關(guān)參數(shù)配置
  • 配置Change Pool最大大小
    ---innodb_change_buffer_max_size: 允許將Change Buffer的最大大小配置為緩沖池總大小的百分比。默認情況下, innodb_change_buffer_max_size設(shè)置為25.最大設(shè)置為50。
  • 配置Change Buffer的適用范圍
    ---innodb_change_buffering: all | none | inserts | deletes | changes | purges

3. Adaptive Hash Index

AHI是InnoDB索引的索引, 為了在索引很大時快速得到數(shù)據(jù)。
AHI 在實現(xiàn)上就是一個哈希表:從某個檢索條件到某個數(shù)據(jù)頁的哈希表,仿佛并不復(fù)雜,但其中的關(guān)竅在于哈希表不能太大(哈希表維護本身就有成本,哈希表太大則成本會高于收益),又不能太小(太小則緩存命中率太低,沒有任何收益)。
AHI建立需要遵循以下約束:
(1)某個索引樹要被使用足夠多次
(2)該索引樹上的某個檢索條件要被經(jīng)常使用
(3)該索引樹上的某個數(shù)據(jù)頁要被經(jīng)常使用

4. Log Buffer

當(dāng)在MySQL中對InnoDB表進行更改時,這些更改首先存儲在InnoDB日志緩沖區(qū)的內(nèi)存中,然后寫入通常稱為重做日志(redo logs)的InnoDB日志文件中。
日志緩沖區(qū)是內(nèi)存存儲區(qū)域,用于保存要寫入磁盤上的日志文件的數(shù)據(jù)。日志緩沖區(qū)大小由innodb_log_buffer_size 變量定義,默認大小為16MB。
日志緩沖區(qū)的內(nèi)容定期刷新到磁盤。較大的日志緩沖區(qū)可以運行大型事務(wù),而無需在事務(wù)提交之前將重做日志數(shù)據(jù)寫入磁盤。因此,如果有更新,插入或刪除許多行的事務(wù),則增加日志緩沖區(qū)的大小可以節(jié)省磁盤I/O。

磁盤部分組件詳解

1. redo log

在更新操作時,會先更新Buffer Pool中的數(shù)據(jù)然后再去操作磁盤,但是在極端情況下會出現(xiàn)系統(tǒng)宕機或者斷電導(dǎo)致磁盤還未更新就丟失了數(shù)據(jù),此時需要把對內(nèi)存所做的修改寫入到一個redo log buffer里去,這里也是一個內(nèi)存緩沖區(qū),用于存放redo日志的。

2. undo log

如果執(zhí)行一個更新語句,且這個語句還在事務(wù)里的話,在事務(wù)提交以前,我們都可以選擇回滾,而這部分回滾的數(shù)據(jù),就是未更新以前的數(shù)據(jù),它是保存在undo日志里的。

3. Change Buffer

--

4. Doublewrite Buffer

如果說 Change Buffer 是提升性能,那么 Doublewrite Buffer 就是保證數(shù)據(jù)頁的可靠性。


怎么理解呢?

前面提到過,MySQL 以「頁」為讀取和寫入單位,一個「頁」里面有多行數(shù)據(jù),寫入數(shù)據(jù)時,MySQL 會先寫內(nèi)存中的頁,然后再刷新到磁盤中的頁。
這時問題來了,假設(shè)在某一次從內(nèi)存刷新到磁盤的過程中,一個「頁」刷了一半,突然操作系統(tǒng)或者 MySQL 進程奔潰了,這時候,內(nèi)存里的頁數(shù)據(jù)被清除了,而磁盤里的頁數(shù)據(jù),刷了一半,處于一個中間狀態(tài),不尷不尬,可以說是一個「不完整」,甚至是「壞掉的」的頁。

有同學(xué)說,不是有 Redo Log 么?其實這個時候 Redo Log 也已經(jīng)無力回天,Redo Log 是要在磁盤中的頁數(shù)據(jù)是正常的、沒有損壞的情況下,才能把磁盤里頁數(shù)據(jù) load 到內(nèi)存,然后應(yīng)用 Redo Log。而如果磁盤中的頁數(shù)據(jù)已經(jīng)損壞,是無法應(yīng)用 Redo Log 的。
所以,MySQL 在刷數(shù)據(jù)到磁盤之前,要先把數(shù)據(jù)寫到另外一個地方,也就是 Doublewrite Buffer,寫完后,再開始寫磁盤。Doublewrite Buffer 可以理解為是一個備份(recovery),萬一真的發(fā)生 crash,就可以利用 Doublewrite Buffer 來修復(fù)磁盤里的數(shù)據(jù)。

代碼塊
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,380評論 2 379

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