對於使用InnoDB儲存引擎的表來說,是以頁為單位來管理儲存空間的,作為記憶體和磁碟之間換入換出的基本粒度。當我們將某頁從磁碟載入到記憶體中,就會進行磁碟I/O。而磁碟I/O的開銷非常影響整體效能,如果我們直接從記憶體中讀取對應的頁,那豈不是減少了磁碟I/O帶來的效能損耗,效率會提升很多。基於此,緩衝池(
Buffer Pool
) 出現了,那麼接下來,我們就來談談InnoDB中的Buffer Pool。
有人會想,既然緩衝池這麼好,那我們將所有資料都儲存到緩衝池中不就好了,不不不,緩衝池是作業系統分配的一片連續的記憶體。而記憶體相比於磁碟的容量小得多,並且價格昂貴。那麼作業系統會給緩衝池分配多少記憶體呢?
#當然,如果你的機器的記憶體容量非常大,可以在設定檔中設定啟動選項參數innodb_buffer_pool_size
單位是位元組,最小不能小於5MB。
緩衝池將作業系統分配的這一連續的內存,劃分成若干個大小預設為16KB的頁(緩衝頁)【此時還沒有真正的磁碟頁被快取到Buffer Pool中】,當我們從磁碟換入一個頁到緩衝池中,如何分配位置呢?因此就需要一些控制資訊來標識這些緩衝池中的緩衝頁,這些控制資訊都存放在一個叫做控制塊的記憶體區域中,與緩衝頁一一對應。控制塊的大小也是固定的。因此在這片連續的記憶體空間中,難免會產生記憶體碎片。綜上,緩衝池的內部結構如下:
上面在控制塊中提到了鍊錶節點訊息,那麼鍊錶節點是用來做什麼的呢?是為了更好的管理緩衝池中的頁。而鍊錶就是用來連結控制塊的,因為控制塊與緩衝頁是一一對應的。
將所有空閒的緩衝頁對應的控制區塊連結起來,形成的鍊錶。
解決的問題:從磁碟換入一個頁到緩衝池中,如何區分緩衝池中的哪個頁是空閒的呢?而有了空閒鍊錶之後,換入一個磁碟頁到緩衝池中時,就直接從空閒鍊錶中取得一個空閒的緩衝頁,並將磁碟頁中對應的資訊填到緩衝頁對應的控制塊中,然後將該控制塊從空閒鍊錶中刪除即可。
若修改了緩衝池中的緩衝頁的數據,導致其與磁碟中資料不一致,該頁稱為髒頁。將所有髒頁對應的控制區塊連結起來形成更新鍊錶,在將來的某個時間根據該鍊錶將對應快取頁的資料刷新到磁碟中。
緩衝池的大小是有限的,如果快取的頁超出了緩衝池的大小,即沒有空閒的緩衝頁了,當有新的頁要添加到緩衝池時,採取LRU的策略將舊的緩衝頁從緩衝池中移除,然後將新的頁加入進來。由於LRU鍊錶涉及的內容較多,我們接下來單獨介紹。
在I/O上的最佳化機制,預讀顧名思義,會異步地把某有些頁面載入到緩衝池中,預計很快就會需要這些頁面,這些請求在一個範圍內引入所有頁面,就是所謂的局部性原理
,目的是減少磁碟I/O。
在了解預讀機制之前,先回顧InnoDB邏輯儲存單元:表空間(tablespace)→段(segment )→區(extent)→頁(page)。其中特意提一下區,後面會用到:一個區就是物理位置上連續的64頁
,也就是一個區的大小是1MB.
利用LRU演算法對最近最少使用的緩衝頁進行管理,形成對應的鍊錶,方便用於淘汰。
當訪問一個頁【即最近訪問】
那麼為什麼InnoDB不使用這麼直觀的LRU演算法呢?原因如下:
預讀失效
預先閱讀緩衝池中的頁都會放到LRU鍊錶的頭部,但其中很多頁可能並不會被讀取。
緩衝池污染
很多使用頻率較低的頁載入到緩衝池中,會把使用頻率較高的頁從緩衝池中淘汰掉。例如全表掃描
基於上述缺點,優化後的具體方法將傳統LRU鍊錶劃分為兩部分:熱資料區域【年輕區】&冷資料區域【老年區】
#結構簡圖如下所示:
如圖所示,熱資料區域與冷資料區域分別佔用不同比例,那麼我們可以透過innodb_old_blocks_pct
啟動選項來控制冷資料區域所佔比例。
改進後的LRU如何更好的解決預讀失敗問題呢?
改進後的LRU如何更好的解決緩衝池污染問題呢?
先說結論,並沒有很好的最佳化這個問題,原因如下【以全表掃描為例】:
那麼到底該如何解決緩衝池污染問題呢?
innodb_old_blocks_time
參數【單位ms】來設置,預設1000ms,而1s會篩選掉大部分像全表掃描這樣的操作。例如在一次全表掃描過程中,多次造訪一個頁面的時間間隔不會超過1s。 緩衝池和查詢快取是一個東西嗎? →不是
【相關推薦:mysql影片教學】
以上是帶你了解MySQL中的資料庫緩衝池(Buffer Pool)的詳細內容。更多資訊請關注PHP中文網其他相關文章!