如果你打算研究研究 MySQL 的 InnoDB 存儲引擎架構(gòu)實(shí)現(xiàn),在啃書之前,讓我先來幫你捋捋思路。
先了解一下 InnoDB 在 MySQL 架構(gòu)中的位置:

MySQL 上層是每個 RMDB 都有的功能如 SQL 分析器和優(yōu)化器等,下層的存儲引擎主要負(fù)責(zé)底層物理結(jié)構(gòu)的實(shí)現(xiàn),上層為下層預(yù)定義了一套 API 接口,可根據(jù)不同需求為每個表指定不同的存儲引擎。
InnoDB 作為目前 MySQL 的默認(rèn)存儲引擎支持事務(wù),主要設(shè)計(jì)面向在線事務(wù)處理應(yīng)用。本文主要從索引,鎖,事務(wù)和內(nèi)存緩沖池架構(gòu)四個方面介紹一下他們的基本概念。
索引
我們查字典的時候不會一頁一頁翻,而是從目錄找第一個字母所在的頁,然后在頁里找單詞。數(shù)據(jù)庫也是一樣,在 InnoDB 里面,對于每張表,以每行記錄的主鍵(若沒定義,MySQL 會幫你創(chuàng)建一個)排序做一個 B+Tree。
為什么用 B+Tree?我們知道,磁盤IO相對于內(nèi)存IO是非常耗時的,而 B+Tree 可以很好控制樹的高度進(jìn)而控制磁盤IO的次數(shù),以保證讀取性能。
B+Tree 的葉子節(jié)點(diǎn)不僅存儲了行的主鍵,還存儲了這行記錄的所有列數(shù)據(jù),這種方式稱之為聚簇索引。
- 優(yōu)勢:按主鍵排序和范圍查效率很高,因?yàn)檎业街麈I就直接拿到數(shù)據(jù)了。
- 缺點(diǎn):改主鍵和亂序插入效率不高。
輔助索引和主鍵索引一樣,也是以 B+Tree 存儲的,例如:key (city, age) 先按 city 排序,再按 age 排序。葉子節(jié)點(diǎn)只存儲了 city,age 和該行的主鍵值。這種結(jié)構(gòu)導(dǎo)致了:
- 通過輔助索引查找整行數(shù)據(jù)時得再次查詢聚簇索引。
-
select * from my_table where age = 17是不能利用key(city, age)的。
如果直接利用輔助索引能得出結(jié)果,這種索引情況叫作覆蓋索引。比如:select id from my_table where city = "北京" order by age,因?yàn)椴恍枰俨榫鄞厮饕?,所以減少了磁盤IO次數(shù),提高了效率。
輔助索引雖能提高查詢效率,但同時也會增加修改數(shù)據(jù)的負(fù)擔(dān),試想一個一百萬行數(shù)據(jù)的表,每次數(shù)據(jù)都得更新一個百萬節(jié)點(diǎn)索引樹。為了提高輔助索引的更新效率,InnoDB 內(nèi)部采用 Insert/Change Buffer 機(jī)制,可理解為先將對輔助的修改緩存起來,通過 merge 操作把單個隨機(jī)修改轉(zhuǎn)換成多個順序修改提升性能。這種技術(shù)有個限制:修改的索引不能有唯一約束,想想也是,如果有唯一性限制,每個修改操作必須依賴整棵 B+Tree 的狀態(tài)。
除此之外,InnoDB 內(nèi)部還有自適應(yīng)哈希索引(AHI),如字面意思,如果我們老是通過某個索引查詢數(shù)據(jù),InnoDB 就會把這個索引加入一個hash表中,這樣相同的查詢就是 O(1) 的復(fù)雜度了。
事務(wù)
事務(wù)代表了一次數(shù)據(jù)庫操作執(zhí)行單元,可以是一條 SQL語句,也可以是多條 SQL 語句組合。所謂執(zhí)行單元是指要么每條 SQL 全執(zhí)行,要么都不執(zhí)行。事務(wù)最為嚴(yán)格的是要滿足 Atomicity Consistency Isolation Durability 特性。
Isolation 隔離性
InnoDB 默認(rèn)隔離級別是 REPEATABLE READ,使用 Next-key Lock 算法,它是一種鎖算法,結(jié)合了 Record Lock 和 Gap Lock。
Record Lock 顧名思義,鎖一行,通常是鎖主鍵記錄。Gap Lock 鎖一個范圍但不包括本身,通常鎖非唯一索引,主要目的是防止多個事務(wù)將記錄插入到統(tǒng)一范圍,而這會導(dǎo)致幻讀。所以 InnoDB 的 REPEATABLE READ 已達(dá)到 SERIALIZABLE 的隔離級別。
需要注意的是,執(zhí)行 select * from my_table where city = "北京" for update, 如果 key (city, age) 沒有命中,此時會鎖住整個 my_table 表。
另一方面,鎖是性能第一大殺手。為了提升事務(wù)性能,InnoDB 實(shí)現(xiàn)了一致性非鎖定讀,它是指當(dāng)一個事務(wù)讀取的行在另一個事務(wù)中正被 DELETE 或者 UPDATE 時,讀取操作不需要等待鎖釋放,而是讀取該行的一個歷史快照數(shù)據(jù)。這種行數(shù)據(jù)帶有多個歷史版本的技術(shù)叫做多版本并發(fā)控制(MVCC)。典型的空間換時間策略。
Durability 持久性
InnoDB 在每次事務(wù) COMMIT 時,會將在本次事務(wù)所有對底層文件的物理操作以日志文件的形式存儲到文件系統(tǒng),只有當(dāng)這個操作成功后,才能認(rèn)為本次事務(wù) COMMIT 成功。這個日志叫做重做日志 (redo log),如果數(shù)據(jù)庫在事務(wù)修改數(shù)據(jù)時發(fā)生故障,我們可以用它把事務(wù)的物理操作再執(zhí)行一遍,這樣就保證了事務(wù)的持久性。
Atomicity 原子性
原子性是指:在一個事務(wù)執(zhí)行過程中,出現(xiàn)錯誤或執(zhí)行 CALLBACK,需要將數(shù)據(jù)庫恢復(fù)到事務(wù)開始的狀態(tài)。InnoDB 中采用撤銷日志(undo log)實(shí)現(xiàn)原子性,它保存了事務(wù)修改操作的邏輯反操作,例如在執(zhí)行 update my_table set age = 23 where id = 10 的同時會生成相應(yīng)的 undo log update my_table set age = 17 where id = 10。undo log 存在了 MySQL 的一張表里,而不是文件中。
緩沖池
InnoDB 作為存儲引擎接受來自 MySQL 上層的調(diào)用,最終修改底層文件數(shù)據(jù)。如果每次操作都立刻同步到文件系統(tǒng),頻繁的磁盤IO會嚴(yán)重?cái)?shù)據(jù)庫性能,所以 InnoDB 維護(hù)了一個內(nèi)存緩沖池。執(zhí)行讀操作時,首先把磁盤讀到的頁放在緩存池中,下次相同頁的讀操作會直接從緩存中拿數(shù)據(jù)。同樣的,執(zhí)行修改操作會先對緩沖池的數(shù)據(jù)頁修改,等待時機(jī)刷新回磁盤。我們之前提到的 Insert Buffer 和 AHI 也會在緩沖池做緩存。
緩沖池可以很好的提高性能,也帶了新的問題。我們把數(shù)據(jù)頁的修改放在了內(nèi)存,如果機(jī)器宕機(jī),有些數(shù)據(jù)不就丟失了?別怕,我們不是已經(jīng)存了重做日志,它可以幫我們把數(shù)據(jù)庫同步到宕機(jī)時的狀態(tài)。
既然數(shù)據(jù)是安全,那試想一下如果我們的內(nèi)存足夠大,為重做日志提供足夠的磁盤空間,是不是就意味著我們的大量磁盤IO都可以放在內(nèi)存執(zhí)行?那運(yùn)行性能絕對很高。但,這并不現(xiàn)實(shí),沒有誰總能有足夠的內(nèi)存和精確地評估重做日志的增長速度。而且即使?jié)M足了這兩個條件,如果數(shù)據(jù)庫在長時間運(yùn)行后宕機(jī),重新利用重做日志恢復(fù)數(shù)據(jù)的過程也會非常耗時。
InnoDB 針對緩沖池的數(shù)據(jù)頁刷新回磁盤的時機(jī)控制采用 CheckPoint 機(jī)制。就是在一些條件下觸發(fā)回寫磁盤操作,同時記錄當(dāng)前數(shù)據(jù)庫的數(shù)據(jù)版本。緩沖池不夠用,重寫日志磁盤空間緊張都會強(qiáng)制執(zhí)行同步磁盤。
剛剛提到數(shù)據(jù)恢復(fù)的問題,我們可以通過重寫日志同步數(shù)據(jù)庫狀態(tài)。但有個極端情況:某一頁在進(jìn)行磁盤同步的時候機(jī)器宕機(jī),物理數(shù)據(jù)頁損壞,內(nèi)存數(shù)據(jù)頁數(shù)據(jù)丟失,無法恢復(fù)。為了解決這問題,InnoDB 采用了 doublewrite 技術(shù),其實(shí)還是 Write Ahead Log 策略,在數(shù)據(jù)頁寫入磁盤前,先將內(nèi)存中準(zhǔn)備操作的數(shù)據(jù)頁(128個共2M) memcpy 到內(nèi)存中的 doublewirte buffer 中,接著立刻將這 2M fsync 到磁盤(順序?qū)?,速度?,然后再把 doublewirter buffer 中的各個頁寫入到磁盤中(離散寫,速度慢)。有了這個機(jī)制,我們就可以從磁盤找到損壞頁的副本恢復(fù)數(shù)據(jù)頁,保障了數(shù)據(jù)的安全性。
總結(jié)
以上針對 InnoDB 的幾個主要技術(shù)特性做了簡要的描述,同樣重要但沒有涉及到的還有:后臺線程,表的組織和存放,數(shù)據(jù)的備份和復(fù)制,不同應(yīng)用場景下的硬件需求等。
深入學(xué)習(xí)需要好書和實(shí)踐,我推薦《高性能 MySQL》《MySQL 技術(shù)內(nèi)幕 InnoDB 存儲引擎》《索引的設(shè)計(jì)和優(yōu)化原理》。