1.1線程

InnoDB引擎是多線程模型,因此后臺(tái)有很多線程:
- Master Thread:非常核心的一個(gè)線程,主要將緩存池中的數(shù)據(jù)異步刷新到磁盤,包括臟頁刷新,合并插入緩存(insert buffer),undo頁的回收。
- IO Thread:InnoDB使用大量AIO(Async io)來處理寫請求,而IO Thread主要工作就是負(fù)責(zé)這些IO請求的回調(diào),InnoDB1.0版本前有4個(gè)IO Thread,分別是write,read,insert buffer,log io thread,從1.0.x版本開始,read thread和write thread分別增大到4個(gè),可以根據(jù)參數(shù)innodb_read_io_threads和innodb_write_io_threads來設(shè)置讀寫線程,并且讀線程ID要小于寫線程
- Purge Thread:事務(wù)提交后,其所使用的undolog可能不再使用,因?yàn)樾枰狿urge Thread回收已經(jīng)使用的undo頁,1.1版本前,purge僅在Master Thread中完成,1.1版本后,單獨(dú)提出來Purge Thread
- Page Cleaner thread:作用將臟頁刷新放入單獨(dú)線程中。
1.2內(nèi)存
1.2.1緩沖池
InnoDB基于磁盤儲(chǔ)存,將數(shù)據(jù)按照頁的方式管理,必然cpu和磁盤速度之間會(huì)有差異,于是緩沖池技術(shù)就是應(yīng)對這種情況而誕生的。
其實(shí)緩沖池就是一塊內(nèi)存區(qū)域,在數(shù)據(jù)庫中進(jìn)行讀取頁的操作,首先將磁盤讀到的頁存放到緩沖池中,下次再讀取會(huì)判斷該頁是否在緩沖池,如果在就稱該頁在緩沖池被命中,否則讀取磁盤。
對于數(shù)據(jù)庫頁的修改,首先修改緩沖池的頁,再以一定的評率刷新到磁盤,這里是通過一種Checkpoint機(jī)制刷新到磁盤中。緩沖池配置通過innodb_buffer_pool_size來設(shè)置。

從 InnoDB 1.0.x 版本開始,允許有多個(gè)緩沖池實(shí)例。每個(gè)頁根據(jù)哈希值平均分配到不同緩沖池實(shí)例中。這樣做的好處是減少數(shù)據(jù)庫內(nèi)部的資源競爭,增加數(shù)據(jù)庫的并發(fā)處理能力
1.2.2.LRU List、Free List、Flush List
數(shù)據(jù)庫中的緩沖池是通過 LRU ( Latest Recent U sed,最近最少使用〉 算法來進(jìn)行管理的 。即最頻繁使用的頁在 LRU 列表的前端,而最少使用的頁在 LRU 列表的尾端。當(dāng)緩沖池不能存放新讀取到的頁時(shí) ,將首先釋放LRU 列表中尾端的頁。
在 InnoDB 存儲(chǔ)引擎中,緩沖池中頁的大小默認(rèn)為 16KB,同樣使用 LRU 算法但是做了優(yōu)化,LRU 列表中還加入了midpoint 位置。新讀取到的頁,雖然是最新訪問的頁,但并不是直接放入到 LRU 列表的首部,而是放入到 LRU 列表的midpoint
,默認(rèn)值是5/8,參數(shù) innodb_old_blocks_pct控制,midpoint 之后的列表稱為 old 列表,之前的列表稱為 new 列表。
那為什么不采用樸素的 LRU 算法,直接將讀取的頁放入到 LRU 列表的首部呢?這 是因?yàn)槿糁苯訉⒆x取到的頁放入到 LRU 的首部,那么某些 SQL 操作可能會(huì)使緩沖池中 的頁被刷新出,從而影響緩沖池 的效率。常見的這類操作為索引或數(shù)據(jù)的掃描操作。這 類操作需要訪問表中的許多頁,甚至是全部的頁,而這些頁通常來說又僅在這次查詢操 作中需要,并不是活躍的熱點(diǎn)數(shù)據(jù)。如果頁被放人 LRU 列表的首部,那么非??赡軐⑺枰臒狳c(diǎn)數(shù)據(jù)頁從 LRU 列表中移除,而在下一次需要讀取該頁時(shí),InnoDB 存儲(chǔ)引擎需要再次訪問磁盤。
為了解決這個(gè) 問題,lnnoDB 存儲(chǔ)引擎引人了另一個(gè)參數(shù)來進(jìn)一步管理 LRU 列表, 這個(gè)參數(shù)是 innodb_old_blocks_time ,用于表示頁讀取到 mid 位置后需要等待多久才會(huì)被 加人到 LRU 列表的熱端。
需要注意的是因?yàn)榫彌_池中的頁還可能會(huì)被分配給自適應(yīng)哈希索引、Lock 信息、Insert Buffer 等頁,而這部分頁不需要 LRU 算法進(jìn)行維護(hù),因此不存在于 LRU 列表中??梢越Y(jié)合上面的圖查看。
前面提到的緩沖池命中率也就是Buffer pool hit rate,如果這個(gè)值太低,一般都是90%以上,就需要考慮下是否是全表掃描引起的LRU列表被污染的問題,比如唯一索引修改為普通索引,導(dǎo)致sql語句走了全表掃描。
innodb從1.0.x版本開始支持壓縮頁的功能,即將原本16KB的頁壓縮為1,2,4,8KB,對于非16KB的頁,是通過unzip_LRU列表管理,要注意的是LRU中的頁包含了unzip_LRU列表中頁。
在 LRU 列表中的頁被修改后,稱該頁為臟頁 ( dirty page ),即緩沖池中的頁和磁盤 上的頁的數(shù)據(jù)產(chǎn)生了不一致。這時(shí)數(shù)據(jù)庫會(huì)通過 CHECKPOINT 機(jī)制將臟頁刷新回磁盤, 而 Flush 列表中的頁即為臟頁列表。需要注意的是,臟頁既存在于 LRU 列表中,也存在于 Flush 列表中。LRU 列表用來管理緩沖池中頁的可用性,F(xiàn)lush 列表用來管理將頁刷新回磁盤,二者互不影響。
1.2.3redo log buffer(重做日志緩沖)
InnoDB 存儲(chǔ)引擎的內(nèi)存區(qū)域除了有緩沖池外,還有redo log buffer。lnnoDB 存儲(chǔ)引擎首先將重做日志信息先放人到這個(gè)緩沖區(qū),然后刷新到重做日志文件。重做日志緩沖一般不需要設(shè)置得很大,因?yàn)橐话闱闆r下每一秒鐘會(huì)將重做日志緩沖刷新到日志文件,因此用戶只需要保證每秒產(chǎn)生的事務(wù)量在這個(gè)緩沖大小之內(nèi)即可 。該值可由配置參數(shù) innodb_log_buffer_size 控制。
redo log buffer會(huì)在下面三種情況被刷新到磁盤的重做日志文件中:
- Master Thread 每一秒將重做日志緩沖刷新到重做日志文件
- 每個(gè)事務(wù)提交時(shí)會(huì)將重做日志緩沖刷新到重做日志文件
- 當(dāng)重做日志緩沖池剩余空間小于 1/2 時(shí),重做日志緩沖刷新到重做日志文件
1.3Checkpoint
倘若每次一個(gè)頁發(fā)生變化 ,就將新頁的版本刷新到 磁盤,那么這個(gè)開銷是非常大 的。若熱點(diǎn)數(shù)據(jù)集中在某幾個(gè)頁中,那么數(shù)據(jù)庫的性能將變得非常差 。同時(shí),如果在從緩沖池將頁的新版本刷新到磁盤時(shí)發(fā)生了宕機(jī) ,那么數(shù)據(jù)就不能恢復(fù)了。為了避免發(fā)生數(shù)據(jù)丟失的問題,當(dāng)前事務(wù)數(shù)據(jù)庫系統(tǒng)普遍都采用了Write Ahead Log 策略,即當(dāng)事務(wù)提交時(shí),先寫重做日志 ,再修改頁。當(dāng)由于發(fā)生者機(jī)而導(dǎo)致數(shù)據(jù)丟失時(shí),通過重做日志來完成數(shù)據(jù)的恢復(fù)。這也是事務(wù) ACID 中D ( Durability 持久性〉 的要求。
我們需要明白如下幾點(diǎn):
- 緩沖池不可能緩存數(shù)據(jù)庫中所有數(shù)據(jù),也許數(shù)據(jù)庫在重啟后的最開始一段時(shí)間能做到。
- 重做日志不可以無限增大
因此 Checkpoint (檢查點(diǎn)〉 技術(shù)的目的是解決以下幾個(gè)問題 - 縮短數(shù)據(jù)庫的恢復(fù)時(shí)間
- 緩沖池不夠用時(shí),將臟頁刷新到磁盤
- 重做日志不可用時(shí),刷新臟頁
當(dāng)數(shù)據(jù)庫發(fā)生宕機(jī)時(shí),數(shù)據(jù)庫不需要重做所有的日志,因?yàn)?Checkpoint 之前的頁都已經(jīng)刷新回磁盤。故數(shù)據(jù)庫只需對 Checkpoint 后的重做日志進(jìn)行恢復(fù)。這樣就大大縮短了恢復(fù)的時(shí)間。
此外,當(dāng)緩沖池不夠用時(shí),根據(jù) LRU 算法會(huì)溢出最近最少使用的頁,若此頁為臟 頁,那么需要強(qiáng)制執(zhí)行 Checkpoint ,將臟頁也就是頁的新版本刷回磁盤。
重做日志出現(xiàn)不可用的情況是因?yàn)楫?dāng)前事務(wù)數(shù)據(jù)庫系統(tǒng)對重做日志的設(shè)計(jì)都是循 環(huán)使用的,并不是讓其無限增大的,這從成本及管理上都是比較困難的。重做日志可以被重用的部分是指這些重做日志已經(jīng)不再需要,即當(dāng)數(shù)據(jù)庫發(fā)生宕機(jī)時(shí),數(shù)據(jù)庫恢復(fù)操作不需要這部分的重做日志,因此這部分就可以被覆蓋重用。若此時(shí)重做日志還需要使用,那么必須強(qiáng)制產(chǎn)生 Checkpoint ,將緩沖池中的頁至少刷新到當(dāng)前重做日志的位置。
需要注意的關(guān)于redo log有這樣幾點(diǎn)
- 日志緩沖刷新到磁盤,即使這個(gè)事務(wù)還沒有提交(總是),這也是為什么很大的事務(wù)的提交時(shí)間也是很短的。
- 合并插入緩沖(可能)(這也導(dǎo)致了mysql server層面的binlog機(jī)制和這個(gè)redo log可能數(shù)據(jù)會(huì)對不上,即使是二階段提交協(xié)議下)
2.innodb更多特性
- Change Buffer(之前是Insert Buffer)
- 兩次寫 ( Double Write )
- 自適應(yīng)哈希索引 ( Adaptive Hash Index )
- 異步 IO (Async IO)
- 刷新鄰接頁 ( Flush Neighbor Page)
2.1Change Buffer
lnnoDB 存儲(chǔ)引擎可以對DML 操作:INSERT、DELETE 、UPDATE 都進(jìn)行緩沖,他們分別是:Insert Buffer、Delete Buffer、Purge Buffer。
關(guān)于Delete Buffer、Purge Buffer這里提一句:當(dāng)你在刪除某條記錄時(shí),分為兩步:第一步是Delete Buffer將記錄標(biāo)記為刪除,Purge Buffer將記錄真正刪除。
Change Buffer 是干什么的了?Change Buffer是一種應(yīng)用在非唯一普通索引頁(non-unique secondary index page)不在緩沖池中,對頁進(jìn)行了寫操作,并不會(huì)立刻將磁盤頁加載到緩沖池,而僅僅記錄緩沖變更(buffer changes),等未來數(shù)據(jù)被讀取時(shí),再將數(shù)據(jù)合并(merge)恢復(fù)到緩沖池中的技術(shù)。寫緩沖的目的是降低寫操作的磁盤IO,提升數(shù)據(jù)庫性能。
那么還有一個(gè)問題:為何只適合非唯一普通索引?
很簡單,如果索引設(shè)置了唯一(unique)屬性,在進(jìn)行修改操作時(shí),InnoDB必須進(jìn)行唯一性檢查。也就是說,索引頁即使不在緩沖池,磁盤上的頁讀取無法避免。
所以不適合開啟innodb的寫緩沖機(jī)制的場景是:
- 數(shù)據(jù)庫都是唯一索引
- 寫入一個(gè)數(shù)據(jù),需要馬上讀取它
相反適合開啟Change buffer的場景是: - 數(shù)據(jù)庫大部分索引是非唯一索引
- 寫多讀少(比如賬單流水,日志記錄)
2.2Double Write
關(guān)于IO的最小單位:
- 數(shù)據(jù)庫IO的最小單位是16K(MySQL默認(rèn),oracle是8K)
- 文件系統(tǒng)IO的最小單位是4K(也有1K的)
- 盤IO的最小單位是512字節(jié)
doublewrite:兩次寫提高innodb的可靠性,用來解決部分寫失敗(partial page write頁斷裂)。
比如這樣的場景:當(dāng)發(fā)生數(shù)據(jù)庫著機(jī)時(shí),可能lnnoDB 存儲(chǔ)引擎正在寫人某個(gè)頁到表中,而這個(gè)頁只寫了一部分,比如 16KB 的頁,只寫了前 4KB ,之后就發(fā)生了宕機(jī),這種情況被稱為部分寫失效 (partial page write )(redo log重做日志記錄的是對頁的物理修改,如果頁本身已經(jīng)損壞,重做日志也無能為力)
doublewrite由兩部分組成,一部分為內(nèi)存中的doublewrite buffer,其大小為2MB,另一部分是磁盤上共享表空間(ibdata x)中連續(xù)的128個(gè)頁,即2個(gè)區(qū)(extent),大小也是2M。
- 當(dāng)一系列機(jī)制觸發(fā)數(shù)據(jù)緩沖池中的臟頁刷新時(shí),并不直接寫入磁盤數(shù)據(jù)文件中,而是先拷貝至內(nèi)存中的doublewrite buffer中;
- 接著從兩次寫緩沖區(qū)分兩次寫入磁盤共享表空間中(連續(xù)存儲(chǔ),順序?qū)?,性能很?,每次寫1MB;
-
待第二步完成后,再將doublewrite buffer中的臟頁數(shù)據(jù)寫入實(shí)際的各個(gè)表空間文件(離散寫);(臟頁數(shù)據(jù)固化后,即進(jìn)行標(biāo)記對應(yīng)doublewrite數(shù)據(jù)可覆蓋)
double write工作流程
如果操作系統(tǒng)在將頁寫入磁盤的過程中發(fā)生崩潰,在恢復(fù)過程中,innodb存儲(chǔ)引擎可以從共享表空間的doublewrite中找到該頁的一個(gè)最近的副本,將其復(fù)制到表空間文件,再應(yīng)用redo log,就完成了恢復(fù)過程。因?yàn)橛懈北舅砸膊粨?dān)心表空間中數(shù)據(jù)頁是否損壞。(redolog寫入的單位就是512字節(jié),也就是磁盤IO的最小單位,所以無所謂數(shù)據(jù)損壞,所以不需要doublewrite的支持)
2.3自適應(yīng)哈希索引
哈希是一種非??斓牟檎曳椒?,在一般情況下這種查找的時(shí) 間復(fù)雜度為 0(1), 一般僅需要一次查找就能定位數(shù)據(jù) 。而 B+ 樹的查找次數(shù),取決于 B+ 樹的高度,在生產(chǎn)環(huán)境中,B+樹的高度一般為 3、4 層,故需要 3、4 次的查詢。
InnoDB 存儲(chǔ)引擎會(huì)監(jiān)控對表上各索引頁的查詢。如果觀察到建立哈希索引可以帶 來速度提升 ,則建立哈希索引,稱之為自適應(yīng)哈希索引 (Adaptive Hash Index, AHi)。
2.4異步IO
如其明,這里不多說。
2.5刷新鄰接頁
innoDB 存儲(chǔ)引擎還提供了Flush Neighbor Page (刷新鄰接頁〉 的特性。其工作原理刷新一個(gè)臟頁時(shí),InnoDB 存儲(chǔ)引擎會(huì)檢測該頁所在區(qū)的所有頁,如果是臟頁,那么一起進(jìn)行刷新。這種做法在機(jī)械硬盤時(shí)代是由顯著的優(yōu)勢的。但也會(huì)出現(xiàn)下面問題:
- 會(huì)將不怎么臟的頁頻繁寫入,該頁又會(huì)很快變成臟頁
- 固態(tài)硬盤有著很高的io能力,這個(gè)參數(shù)意義不大
本文主要是自己看書和專欄文章來寫,其實(shí)更多算是一種筆記。以后方便記憶和復(fù)習(xí)。
