什么是MySQL的"內(nèi)存數(shù)據(jù)加速器"——Buffer Pool?
我們都知道,MySQL 的數(shù)據(jù)(除了 Memory 引擎外)都存儲在磁盤上。然而,若每次查詢和修改都直接與磁盤交互,性能將會非常低下。
因此,為了提升讀寫性能,Innodb 引擎引入了一個(gè)中間層,即緩沖池(buffer pool)。
緩沖池是內(nèi)存中一塊連續(xù)的空間,主要用于緩存數(shù)據(jù)頁。每個(gè)數(shù)據(jù)頁的大小為 16KB。
頁是 Innodb 進(jìn)行數(shù)據(jù)存儲的基本單元,無論是在磁盤還是在緩沖池中,數(shù)據(jù)的讀取都是以頁為單位進(jìn)行的,這也體現(xiàn)了一種“預(yù)讀”的思想。
圖片
有了緩沖池之后,當(dāng)我們進(jìn)行數(shù)據(jù)查詢時(shí),InnoDB 會首先檢查緩沖池中是否存在該數(shù)據(jù)。如果存在,數(shù)據(jù)就可以直接從內(nèi)存中獲取,避免了頻繁的磁盤讀取,從而提高查詢性能。如果不存在,則會去磁盤中讀取數(shù)據(jù),并將找到的數(shù)據(jù)頁復(fù)制到緩沖池中,再返回給客戶端。這樣,后續(xù)的查詢可以直接從緩沖池中就近讀取數(shù)據(jù)。
圖片
當(dāng)需要進(jìn)行數(shù)據(jù)修改時(shí),操作也會先在緩沖池中進(jìn)行,然后再將修改后的數(shù)據(jù)寫入磁盤。
然而,由于緩沖池是基于內(nèi)存的,其空間不可能無限大,默認(rèn)大小為 128M。當(dāng)然,這個(gè)大小并不是固定的,我們可以通過修改 MySQL 配置文件中的 innodb_buffer_pool_size 參數(shù)來調(diào)整緩沖池的大小。
# 查看buffer pool
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
# 修改buffer pool
SET GLOBAL innodb_buffer_pool_size = 512M;
擴(kuò)展知識
InnoDB 的數(shù)據(jù)頁
上面提到了 InnoDB 的數(shù)據(jù)頁,它和 B+樹的關(guān)系是怎樣的呢?
InnoDB 的數(shù)據(jù)頁是其存儲引擎中用于存儲數(shù)據(jù)的基本單位。數(shù)據(jù)頁在磁盤上是一個(gè)連續(xù)的區(qū)域,通常大小為 16KB,當(dāng)然也可以通過配置進(jìn)行調(diào)整。16KB 意味著 InnoDB 的每次讀寫操作都是以 16KB 為單位的,即一次從磁盤讀取到內(nèi)存的最小單位是 16KB,從內(nèi)存寫入到磁盤的最小單位也是 16KB。
在 B+樹結(jié)構(gòu)中,每個(gè)節(jié)點(diǎn)都對應(yīng)著一個(gè)數(shù)據(jù)頁,包括根節(jié)點(diǎn)、非葉子節(jié)點(diǎn)和葉子節(jié)點(diǎn)。B+樹通過節(jié)點(diǎn)之間的指針連接不同層級的數(shù)據(jù)頁,從而構(gòu)建出一個(gè)有序的索引結(jié)構(gòu)。
圖片
通過 B+樹的搜索過程,可以從根節(jié)點(diǎn)開始逐層遍歷,最終到達(dá)葉子節(jié)點(diǎn),找到所需的數(shù)據(jù)行。
因此,數(shù)據(jù)頁是存儲數(shù)據(jù)行的實(shí)際物理空間,以頁為單位進(jìn)行磁盤讀寫操作。B+樹通過節(jié)點(diǎn)和指針的組織,構(gòu)建了一個(gè)層次結(jié)構(gòu)的索引,用于快速定位和訪問數(shù)據(jù)行。
B+樹的非葉子節(jié)點(diǎn)對應(yīng)著數(shù)據(jù)頁,其中存儲著主鍵及指向子節(jié)點(diǎn)(即其他數(shù)據(jù)頁)的指針。B+樹的葉子節(jié)點(diǎn)包含實(shí)際的數(shù)據(jù)行,每個(gè)數(shù)據(jù)行存儲在一個(gè)數(shù)據(jù)頁中。
通過這種方式,InnoDB 利用 B+樹和數(shù)據(jù)頁的組合,實(shí)現(xiàn)了高效的數(shù)據(jù)存儲和檢索。B+樹提供了快速的索引查找能力,而數(shù)據(jù)頁提供了實(shí)際存儲和管理數(shù)據(jù)行的機(jī)制。它們相互配合,使得 InnoDB 能夠處理大規(guī)模數(shù)據(jù)的高效訪問。
數(shù)據(jù)頁的構(gòu)成
一個(gè)數(shù)據(jù)頁包含七個(gè)部分,分別是文件頭、頁頭、最小和最大記錄、用戶記錄、空閑空間、頁目錄以及文件尾。
圖片
buffer pool 和 query cache 的區(qū)別
在 InnoDB 中,除了緩沖池(Buffer Pool),還有另一個(gè)緩存層用于數(shù)據(jù)緩存,提升查詢效率。很多人容易混淆它與緩沖池的區(qū)別。
首先,它們的目的和作用不同。緩沖池用于緩存表和索引的數(shù)據(jù)頁,從而加速讀取操作;而查詢緩存(Query Cache)用于緩存查詢結(jié)果,減少重復(fù)查詢的執(zhí)行時(shí)間。
緩沖池主要與存儲引擎 InnoDB 相關(guān),而查詢緩存也支持其他引擎,如 MyISAM 等。因此,查詢緩存位于服務(wù)器層的優(yōu)化技術(shù),而緩沖池位于引擎層的優(yōu)化技術(shù)。
需要注意的是,在 MySQL 5.7 版本中,查詢緩存已經(jīng)被標(biāo)記為廢棄,并在 MySQL 8.0 版本中徹底被移除。
buffer pool 的讀寫過程是怎么樣的?
MySQL 的緩沖池(Buffer Pool)是一個(gè)內(nèi)存區(qū)域,用于緩存數(shù)據(jù)頁,從而提高查詢性能。讀寫過程涉及將數(shù)據(jù)從磁盤讀取到內(nèi)存、在內(nèi)存中進(jìn)行修改,并最終寫回磁盤。
讀過程
當(dāng)我們在 MySQL 執(zhí)行一個(gè)查詢請求時(shí),其過程如下:
- MySQL 首先檢查緩沖池(Buffer Pool)中是否存在本次查詢的數(shù)據(jù)。如果數(shù)據(jù)在緩沖池中,就直接返回結(jié)果。
- 如果數(shù)據(jù)不在緩沖池中,MySQL 會從磁盤讀取數(shù)據(jù)。
- 讀取的數(shù)據(jù)頁被放入緩沖池,同時(shí) MySQL 會將請求的數(shù)據(jù)返回給應(yīng)用程序。
讀取過程相對簡單,而緩沖池的寫入過程則稍顯復(fù)雜。
寫過程
當(dāng)執(zhí)行一次更新語句(如 INSERT、UPDATE 或 DELETE)時(shí),MySQL 的過程如下:
- 應(yīng)用程序執(zhí)行寫操作時(shí),MySQL 首先將要修改的數(shù)據(jù)頁加載到緩沖池(Buffer Pool)中。
- 在緩沖池中,對數(shù)據(jù)頁進(jìn)行修改,以滿足寫請求。這些修改只在內(nèi)存中進(jìn)行,不會立即寫回磁盤。
- 如果緩沖池中的數(shù)據(jù)頁被修改過,MySQL 會將這個(gè)頁標(biāo)記為“臟頁”(Dirty Page)。
- 臟頁會被后臺線程寫回磁盤,這個(gè)過程稱為臟頁刷盤。寫入操作完成后,數(shù)據(jù)得以持久化。
需要注意的是,臟頁的寫回磁盤是由后臺線程進(jìn)行的。在 MySQL 服務(wù)器空閑或負(fù)載較低時(shí),InnoDB 會執(zhí)行臟頁刷盤操作,以減少對用戶線程的影響,從而降低性能的影響。
參考文檔:https://dev.mysql.com/doc/refman/8.0/en/innodb-buffer-pool-flushing.html
圖片
當(dāng)臟頁的百分比達(dá)到innodb_max_dirty_pages_pct_lwm變量定義的低水位標(biāo)記時(shí),將啟動緩沖池的刷新。緩沖池頁的默認(rèn)低水位標(biāo)記為 10%。將innodb_max_dirty_pages_pct_lwm值設(shè)為 0 會禁用這種提前刷新行為。
InnoDB 還采用了一種適應(yīng)性刷新算法,根據(jù) redo log 的生成速度和當(dāng)前的刷新率動態(tài)調(diào)整刷新速度。其目的是通過確保刷新活動與當(dāng)前工作負(fù)載保持同步,來平滑整體性能。
當(dāng)然,我們也可以通過執(zhí)行SET GLOBAL innodb_buffer_pool_dump_now=ON來手動觸發(fā)臟頁刷新到磁盤。
此外,在 MySQL 服務(wù)器正常關(guān)閉或重啟時(shí),所有的臟頁都會被刷新到磁盤,以確保數(shù)據(jù)持久化。