Spring Boot 集成 Liquibase,數(shù)據(jù)庫也能做版本控制!
面是官方的 InnoDB 引擎架構(gòu)圖,主要分為內(nèi)存結(jié)構(gòu)和磁盤結(jié)構(gòu)兩大部分。

一、Buffer Pool 概述
Buffer Pool:緩沖池,簡稱 BP。其作用是用來緩存表數(shù)據(jù)與索引數(shù)據(jù),減少磁盤 IO 操作,提升效率。
Buffer Pool 由緩存數(shù)據(jù)(Page)?和對緩存數(shù)據(jù)頁進(jìn)行描述的控制塊組成,控制塊中存儲(chǔ)著對應(yīng)緩存頁的所屬的表空間、數(shù)據(jù)頁的編號、以及對應(yīng)緩存頁在 Buffer Pool 中的地址等信息。
Buffer Pool 默認(rèn)大小是 128M, 以 Page 頁為單位,Page 頁默認(rèn)大小 16K,而控制塊的大小約為數(shù)據(jù)頁的5%,大概是800字節(jié)。

注:Buffer Pool 大小為 128M 指的就是緩存頁的大小,控制塊則一般占5%,所以每次會(huì)多申請 6M 的內(nèi)存空間用于存放控制塊。
如何判斷一個(gè)頁是否在 BufferPool 中緩存 ?
MySQl 中有一個(gè)哈希表數(shù)據(jù)結(jié)構(gòu),它使用表空間號+數(shù)據(jù)頁號,作為一個(gè) key,然后緩沖頁對應(yīng)的控制塊作為 value。

當(dāng)需要訪問某個(gè)頁的數(shù)據(jù)時(shí),先從哈希表中根據(jù)表空間號和頁號看看是否存在對應(yīng)的緩沖頁。如果有,則直接使用;如果沒有,就從 free 鏈表中選出一個(gè)空閑的緩沖頁,然后把磁盤中對應(yīng)的頁加載到該緩沖頁的位置。
二、Page 頁分類
Page 根據(jù)狀態(tài)可以分為三種類型:

Free Page:空閑 Page,未使用
Clean Page:被使用 Page 但是數(shù)據(jù)沒有修改過
Dirty Page:臟頁,使用過數(shù)據(jù)被修改過,與磁盤數(shù)據(jù)產(chǎn)生不一致
針對上面所說的三種 page 類型,InnoDB 通過三種鏈表結(jié)構(gòu)來維護(hù)和管理。
BP 的底層采用鏈表數(shù)據(jù)結(jié)構(gòu)管理 Page。在 InnoDB 訪問表記錄和索引時(shí)會(huì)在 Page頁 中緩存,以后使用可以減少磁盤 IO 操作,提升效率。
2.1、Page 頁管理之 Free 鏈表
free list:表示空閑緩沖區(qū),管理 free page
Buffer Pool 的初始化過程中,是先向操作系統(tǒng)申請連續(xù)的內(nèi)存空間,然后把它劃分成若干個(gè)控制塊&緩沖頁的鍵值對。
free 鏈表是把所有空閑的緩沖頁對應(yīng)的控制塊作為一個(gè)個(gè)的節(jié)點(diǎn)放到一個(gè)鏈表中,這個(gè)鏈表便稱之為 free 鏈表
基節(jié)點(diǎn):free 鏈表中只有一個(gè)基節(jié)點(diǎn)是不記錄緩存頁信息(單獨(dú)申請空間),它里面就存放了 free 鏈表的頭節(jié)點(diǎn)的地址,尾節(jié)點(diǎn)的地址,還有 free 鏈表里當(dāng)前有多少個(gè)節(jié)點(diǎn)。

磁盤加載頁的流程:
從 free 鏈表中取出一個(gè)空閑的控制塊,對應(yīng)緩沖頁。
把該緩沖頁對應(yīng)的控制塊的信息填上,例如:頁所在的表空間、頁號之類的信息。
把該緩沖頁對應(yīng)的 free 鏈表節(jié)點(diǎn)即控制塊從鏈表中移除。表示該緩沖頁已經(jīng)被使用了。
2.2、Page 頁管理之 Flush 鏈表
flush list: 表示需要刷新到磁盤的緩沖區(qū),管理 dirty page,內(nèi)部 page 按修改時(shí)間排序。
InnoDB 引擎為了提高處理效率,在每次修改緩沖頁后,并不是立刻把修改刷新到磁盤上,而是在未來的某個(gè)時(shí)間點(diǎn)進(jìn)行刷新操作。
所以需要使用到 flush 鏈表存儲(chǔ)臟頁,凡是被修改過的緩沖頁對應(yīng)的控制塊都會(huì)作為節(jié)點(diǎn)加入到 flush 鏈表。
flush 鏈表的結(jié)構(gòu)與 free 鏈表的結(jié)構(gòu)相似

臟頁即存在于 flush 鏈表,也在 LRU 鏈表中,但是兩種互不影響,LRU 鏈表負(fù)責(zé)管理 page 的可用性和釋放,而 flush 鏈表負(fù)責(zé)管理臟頁的刷盤操作。
2.3、Page 頁管理之普通 LRU 鏈表
LRU = Least Recently Used(最近最少使用):就是末尾淘汰法,新數(shù)據(jù)從鏈表頭部加入,釋放空間時(shí)從末尾淘汰。

當(dāng)要訪問某個(gè)頁時(shí),如果不在 Buffer Pool,需要把該頁加載到緩沖池,并且把該緩沖頁對應(yīng)的控制塊作為節(jié)點(diǎn)添加到 LRU 鏈表的頭部。
當(dāng)要訪問某個(gè)頁時(shí),如果在 Buffer Pool中,則直接把該頁對應(yīng)的控制塊移動(dòng)到 LRU 鏈表的頭部。
當(dāng)需要釋放空間時(shí),從最末尾淘汰。
2.4、普通 LRU 鏈表的優(yōu)缺點(diǎn)
優(yōu)點(diǎn): 所有最近使用的數(shù)據(jù)都在鏈表表頭,最近未使用的數(shù)據(jù)都在鏈表表尾,保證熱數(shù)據(jù)能最快被獲取到。
缺點(diǎn):如果發(fā)生全表掃描(比如:沒有建立合適的索引 or 查詢時(shí)使用 select * 等),則有很大可能將真正的熱數(shù)據(jù)淘汰掉。由于MySQL中存在預(yù)讀機(jī)制,很多預(yù)讀的頁都會(huì)被放到 LRU 鏈表的表頭。如果這些預(yù)讀的頁都沒有用到的話,會(huì)導(dǎo)致很多尾部的緩沖頁很快就會(huì)被淘汰。

2.5、Page 頁管理之改進(jìn)型 LRU 鏈表
改進(jìn)型 LRU:鏈表分為 new 和 old 兩個(gè)部分,加入元素時(shí)并不是從表頭插入,而是從中間 midpoint 位置插入(就是說從磁盤中新讀出的數(shù)據(jù)會(huì)放在冷數(shù)據(jù)區(qū)的頭部),如果數(shù)據(jù)很快被訪問,那么 page 就會(huì)向 new 列表頭部移動(dòng),如果數(shù)據(jù)沒有被訪問,會(huì)逐步向old尾部移動(dòng),等待淘汰。

冷數(shù)據(jù)區(qū)的數(shù)據(jù)頁什么時(shí)候會(huì)被轉(zhuǎn)到到熱數(shù)據(jù)區(qū)呢 ?
如果該數(shù)據(jù)頁在 LRU 鏈表中存在時(shí)間超過1s,就將其移動(dòng)到鏈表頭部(鏈表指的是整個(gè)LRU 鏈表)。
如果該數(shù)據(jù)頁在 LRU 鏈表中存在的時(shí)間短于1s,其位置不變(由于全表掃描有一個(gè)特點(diǎn),就是它對某個(gè)頁的頻繁訪問總耗時(shí)會(huì)很短)。
1s這個(gè)時(shí)間是由參數(shù) innodb_old_blocks_time 控制的。
三、Change Buffer
3.1、概述
Change Buffer:寫緩沖區(qū),是針對二級索引(輔助索引) 頁的更新優(yōu)化措施
作用::在進(jìn)行 DML 操作時(shí),如果請求的是輔助索引(非唯一鍵索引)沒有在緩沖池中時(shí),并不會(huì)立刻將磁盤頁加載到緩沖池,而是在 CB 記錄緩沖變更,等未來數(shù)據(jù)被讀取時(shí),再將數(shù)據(jù)合并恢復(fù)到 BP 中。
ChangeBuffer 占用 BufferPool 空間,默認(rèn)占25%,最大允許占50%,可以根據(jù)讀寫業(yè)務(wù)量來進(jìn)行調(diào)整。參數(shù) innodb_change_buffer_max_size ;

3.2、Change Buffer 數(shù)據(jù)更新流程
場景1: 對于唯一索引來說,需要將數(shù)據(jù)頁讀如內(nèi)存,判斷沒有沖突,插入這個(gè)值,語句執(zhí)行結(jié)束;
場景2: 對于普通索引來說,則是將更新記錄在 change buffer 流程如下

更新一條記錄時(shí),該記錄在 bp 存在,直接在 bp 修改,一次內(nèi)存操作。
如果該記錄在 bp 不存在(沒有命中),在不影響數(shù)據(jù)一致性的前提下,InnoDB 會(huì)將這些更新操作緩存在 change buffer 中不用再去磁盤查詢數(shù)據(jù),避免一次磁盤 IO。
當(dāng)下次查詢記錄時(shí),會(huì)將數(shù)據(jù)頁讀入內(nèi)存,然后執(zhí)行 change buffer 中與這個(gè)頁有關(guān)的操作。通過這種方式就能保證這個(gè)數(shù)據(jù)邏輯的正確性。
問題一:為什么寫緩沖區(qū),僅適用于非唯一普通索引頁?
如果在索引設(shè)置唯一性,在進(jìn)行修改時(shí),InnoDB 必須要做唯一性校驗(yàn),因此必須查詢磁盤,做一 次 IO 操作。會(huì)直接將記錄查詢 Buffer Pool 中,然后在緩沖池修改,不會(huì)在 Change Buffer 操作。
問題二:什么情況進(jìn)行 merge?
將 change buffer 中的操作應(yīng)用到原數(shù)據(jù)頁,得到最新結(jié)果的過程稱為?merge。
change buffer,實(shí)際上它是可以持久化的數(shù)據(jù)。也就是說,change buffer 在內(nèi)存中有拷貝,也會(huì)被寫入到磁盤上,以下情況會(huì)進(jìn)行持久化:
訪問這個(gè)數(shù)據(jù)頁會(huì)觸發(fā) merge。
系統(tǒng)有后臺線程會(huì)定期 merge。
在數(shù)據(jù)庫正常關(guān)閉( shutdown )的過程中,也會(huì)執(zhí)行 merge 操作。
3.3、change buffer 使用場景
change buffer 的主要目的就是將記錄的變更動(dòng)作緩存下來,所以在 merge 發(fā)生之前應(yīng)當(dāng)盡可能多的緩存變更信息,這樣 change buffer的優(yōu)勢發(fā)揮的就越明顯。
應(yīng)用場景:對于寫多讀少的業(yè)務(wù)來說,頁面在寫完以后馬上被訪問到的概率比較小,此時(shí) change buffer 的使用效果最好。這種業(yè)務(wù)模型常見的就是賬單類、日志類的系統(tǒng)。
4、Log Buffer
Log Buffer:日志緩沖區(qū),用來保存要寫入磁盤上的 Log 文件(redo/undo)的數(shù)據(jù),日志緩沖區(qū)內(nèi)容定期刷新道磁盤 Log 文件中,日志緩沖區(qū)滿時(shí)會(huì)自動(dòng)將其刷新到磁盤,節(jié)省磁盤 io
Log Buffer 主要記錄 innoDB 引擎日志,在 DML 操作時(shí)會(huì)產(chǎn)生 Redo 和 Undo 日志

Log Buffer 空間滿了,會(huì)自動(dòng)寫入磁盤??梢酝ㄟ^將將 innodb_log_buffer_size 參數(shù)調(diào)大,減少磁盤 IO 頻率
innodb_flush_log_at_trx_commit 參數(shù)控制日志刷新行為,默認(rèn)為1
每隔1秒寫日志文件和刷盤操作(寫日志文件 LogBuffer-->OS cache,刷盤 OS cache-->磁盤文件),最多丟失1秒數(shù)據(jù)
事務(wù)提交,立刻寫日志文件和刷盤,數(shù)據(jù)不丟失,但是會(huì)頻繁 IO 操作
事務(wù)提交,立刻寫日志文件,每隔1秒鐘進(jìn)行刷盤操作
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';

SHOW VARIABLES LIKE 'innodb_log_buffer_size';

5、Adaptive Hash Index
自適應(yīng)哈希索引,用于優(yōu)化 bp 數(shù)據(jù)查詢
6、總結(jié)
本期講了關(guān)于 InnoDB 內(nèi)存結(jié)構(gòu),后續(xù)有機(jī)會(huì)在繼續(xù)介紹 InnoDB 磁盤結(jié)構(gòu)。