緩存的重要性
- 無論是用于存儲用戶數據的索引【聚簇索引、二級索引】還是各種系統數據,都是以頁的形式存放在表空間中【對一個/幾個實際文件的抽象,存儲在磁盤上】
- 如果需要訪問某頁的數據,就會把完整的頁數據加載到內存中【即使只訪問頁中的一條記錄】,在讀寫訪問之后不立即釋放該頁的內存空間,將其緩存起來,下次請求訪問該頁面就可以省下磁盤I/O開銷
Buffer Pool
-
在MySQL服務器啟動時就向操作系統申請一片連續的內存,innodb_buffer_pool_size【不包含控制塊所占的內存空間大小】
BufferPool內部組成 -
連續的內存被劃分為若干個頁面【緩沖頁】,頁面大小與InnoDB表空間使用的頁面大小一致【默認16KB】
-
控制信息【所屬的表空間編號、頁號、緩沖頁在Buffer Pool中的地址、鏈表節點信息等】-方便管理
-
控制信息占用的一塊內存稱為控制塊,與緩沖頁一一對應
-
-
碎片-剩余空間不夠一對控制塊和緩沖頁的大小
- 在debug模式下控制塊占緩沖頁大小的5%左右,在非debug模式下控制塊模塊會更小點
free鏈表管理
-
Buffer Pool初始化過程
- 向操作系統申請Buffer Pool的內存空間
- 劃分為若干對控制塊和緩沖頁【沒有真實的磁盤頁被緩存到Buffer Pool中(還沒用到)】
- 隨著程序運行,磁盤的頁不斷被緩存到Buffer Pool中
- 當磁盤讀取一個頁到緩沖池中,該放到哪個緩沖頁【哪個緩沖頁是空閑的】
- 需要查看緩沖頁對應的控制塊
- 將所有空閑的緩沖頁控制塊作為一個節點放在一個鏈表中【free鏈表、空閑鏈表】
- 為了管理好free鏈表,定義一個基節點【包含鏈表的頭節點地址、尾節點地址、當前鏈表中節點的數量等信息】
- 基節點占用的內存空間不包含在Buffer Pool申請的內存空間內,而是單獨申請的一塊內存空間【一般40字節】
- 將所有空閑的緩沖頁控制塊作為一個節點放在一個鏈表中【free鏈表、空閑鏈表】
- 每當磁盤加載一個頁到Buffer Pool中,就從空閑鏈表中取一個控制塊對應的緩沖頁(從鏈表取控制塊,通過控制塊訪問真正的頁),然后把該緩沖頁對應的free鏈表節點【對應的控制塊】從鏈表中移除,表示該緩沖頁被使用
-
緩沖頁的哈希處理
- 表空間號+頁號【key】來定位一個頁,緩沖頁控制塊【value】
- 需要訪問某個頁的數據時,先從哈希表中根據表空間號+頁號看是否有對應緩沖頁
- 有-直接使用
- 沒有-從free鏈表中選一個空閑的緩沖頁,把磁盤中對應的頁加載到該緩沖頁的位置
-
flush鏈表的管理
- 存儲臟頁的鏈表【Buffer Pool中修改過的,還未刷新到磁盤上的頁】
- 某個緩沖頁對應的控制塊不可能既是free鏈表的節點,也是 flush鏈表的節點【不可能既是臟頁也是空閑頁】
-
LRU鏈表的管理
- Buffer Pool命中率越高越好【命中次數/訪問了n頁】
- 簡單的LRU鏈表
- 使用到某個緩沖頁,就把該頁從磁盤加載到Buffer Pool中時,把該頁對應的控制塊作為節點塞到LRU鏈表的頭部
- 當空閑緩沖頁使用完時,到LRU鏈表的尾部淘汰緩沖頁
- 劃分區域的LRU鏈表
- 簡單LRU鏈表存在情況
- InnoDB提供了預讀服務,認為執行當前請求時,可能會在后面讀取到某些頁面就預先把這些頁面加載到Buffer Pool中,如果預讀成功可以極大提高執行效率,但如果用不到的話,前面所說的鏈表就會存在問題,Buffer Pool的命中率會大大降低【加載到Buffer Pool中的頁不一定被用到】
- 線性預讀
- 如果順序訪問的某個區(extent)的頁面超過了系統變量innodb_read_ahead_threshold【一般默認56,服務器啟動時通過啟動選項來調整,或者使用SET GLOBAL命令來修改該全局變量】,會觸發一次異步讀取下一個區中全部的頁面到Buffer Pool中
- 隨機預讀
- 如果某個區的13個連續頁面被加載到Buffer Pool中【也就是指在后面提到的整個young區域的頭1/4】,無論是否是順序讀取,都會觸發一次異步讀取本區中所有其他頁面到Buffer Pool中的請求
- 全局變量innoddb_random_read_ahead系統變量,默認值為OFF,不會默認開啟隨機預讀功能
- 線性預讀
- 全表掃描,全部葉子節點讀一遍,后續執行其他語句時,又要把前面被淘汰的頁面加載進Buffer Pool中【如果有很多使用頻率低的頁被同時加載到Buffer Pool中,可能會把使用頻率高的頁從Buffer Pool中淘汰掉
- InnoDB提供了預讀服務,認為執行當前請求時,可能會在后面讀取到某些頁面就預先把這些頁面加載到Buffer Pool中,如果預讀成功可以極大提高執行效率,但如果用不到的話,前面所說的鏈表就會存在問題,Buffer Pool的命中率會大大降低【加載到Buffer Pool中的頁不一定被用到】
- 將LRU鏈表分為兩節
-
一部分存儲使用頻率高的緩沖頁【這部分鏈表叫熱數據young區域】
-
另一部分存儲使用頻率低的緩沖頁【冷數據old區域】
-
-
隨著程序的運行,某個節點所屬的區域也可能發生變化
-
old區域大小可通過innodb_old_blocks_pct修改【一般3/8】
-
在對某個處于old區域的緩沖頁進行第一次訪問時,就在它對應的控制塊中記錄下這個訪問時間,如果后續的訪問時間與第一次訪問的時間在innodb_old_blocks_times時間間隔內,那么該頁面就不會從old區域移到young區域的頭部【可能是一次全表掃描的多次訪問】
-
- 進一步優化 LRU鏈表
- 只有被訪問頁位于young區域1/4的后面時,才回被移動到LRU鏈表頭部,可以降低調整LRU鏈表的頻率,提升性能【減少鏈表的節點移動】
- 優化核心:盡量高效地提高Buffer Pool命中率
- 簡單LRU鏈表存在情況