MySQL系列(5)— InnoDB 緩衝池Buffer Pool

系列文章:MySQL系列專欄mysql

緩衝池

經過前面幾篇文章,咱們已經瞭解到 InnoDB 存儲引擎是基於磁盤存儲的,並將其中的記錄按照頁的方式進行管理。但因爲CPU速度與磁盤速度之間的鴻溝,一般都會將數據加載到內存中來操做數據,以此提升數據庫的總體性能。web

InnoDB 設計了一個緩衝池(Buffer Pool),當須要訪問某個頁時,就會把這一頁的數據所有加載到緩衝池中,這樣就能夠在內存中進行讀寫訪問了。對於數據庫中頁的修改操做,也是先修改在緩衝池中的頁,而後再以必定的頻率刷新到磁盤上。有了緩衝池,就能夠省去不少磁盤IO的開銷了,從而提高數據庫性能。sql

注意即便只訪問頁中的一條記錄,也須要把整個頁的數據加載到內存中。前面有說過,經過索引只能定位到磁盤中的頁,而不能定位到頁中的一條記錄。將頁加載到內存後,就能夠經過頁目錄(Page Directory)去定位到某條具體的記錄。數據庫

Buffer Pool 結構

MySQL服務器啓動的時候就會向操做系統申請一片連續的內存,即 Buffer Pool。默認配置下 Buffer Pool 只有128MB 大小,咱們能夠調整 innodb_buffer_pool_size 參數來設置 Buffer Pool 的大小。緩存

例以下面設置了1GB的內存,單位是字節:服務器

[server]
innodb_buffer_pool_size = 1073741824
複製代碼

Buffer Pool 也是按頁來劃分的,默認和磁盤上的頁同樣,都是 16KB 大小。Buffer Pool 中不僅緩存了數據頁,還包括索引頁、undo頁、插入緩衝、自適應哈希索引、InnoDB存儲的鎖信息、數據字典信息等。markdown

爲了管理 Buffer Pool 中的緩存頁,InnoDB 爲每個緩存頁都建立了一些描述信息(元數據),用來描述這個緩存頁。描述信息主要包括該頁所屬的表空間編號、頁號、緩存頁的地址、鏈表節點信息、鎖信息、LSN信息等等。數據結構

這個描述信息自己也是一塊數據,它們佔用的內存大小都是相同的。在 Buffer Pool 中,每一個緩存頁的描述信息放在最前面,各個緩存頁在後面。看起來就像下圖這個樣子。多線程

image.png

另外須要注意下,每一個描述數據大約至關於緩存頁大小的 5%,也就是800字節左右的樣子。而咱們設置的 innodb_buffer_pool_size 並不包含描述數據的大小,實際上 Buffer Pool 的大小會超出這個值。好比默認配置 128MB,那麼InnoDB在爲 Buffer Pool 申請連續的內存空間時,會申請差很少 128 + 128*5% ≈ 134MB 大小的空間。併發

Buffer Pool 中劃分緩存頁的時候,會讓全部的緩存頁和描述數據塊都緊密的挨在一塊兒,前面是描述數據塊,後面是緩存頁,儘量減小內存浪費。可是劃分完後,可能還剩一點點的內存,這點內存放不下任何一個緩存頁了,這就是圖中的碎片空間了。

Free 鏈表

MySQL 數據庫啓動的時候,InnoDB 就會爲 Buffer Pool 申請一片連續的內存空間,而後按照默認的16KB的大小劃分出一個個的頁來,還會按照800字節左右的大小劃分出頁對應的描述數據來。

劃分完成後,Buffer Pool 中的緩存頁都是空的,等執行增刪改查等操做時,纔會把數據對應的頁從磁盤加載到 Buffer Pool 中的頁來。

那怎麼知道哪些頁是空閒的呢?InnoDB 設計了一個 free 鏈表,它是一個雙向鏈表數據結構,這個鏈表的每一個節點就是一個空閒緩存頁的描述信息。

實際上,每一個描述信息中有 free_prefree_next 兩個指針,Free鏈表 就是由這兩個指針鏈接起來造成的一個雙向鏈表。而後 Free鏈表 有一個基礎節點,這個基礎節點存放了鏈表的頭節點地址、尾節點地址,以及當前鏈表中節點數的信息。

Free鏈表 看起來就像下圖這樣:

image.png

須要注意的是,鏈表的基礎節點佔用的內存空間並不包含在 Buffer Pool 以內,而是單獨申請的一塊內存空間,每一個基節點只佔用40字節大小。後邊介紹的其它鏈表的基礎節點也是同樣的,都是單獨申請的一塊40字節大小的內存空間。

有了這個 Free鏈表 以後,當須要從磁盤加載一個頁到 Buffer Pool 時,就從 Free鏈表 中取出一個描述數據塊,而後將頁寫入這個描述數據塊對應的空閒緩存頁中。並把一些描述數據寫入描述數據塊中,好比頁的表空間號、頁號之類的。最後,把緩存頁對應的描述數據塊從 Free鏈表 中移除,表示該緩存頁已被使用了。

能夠看到,從磁盤加載一個頁到緩存頁後,緩存頁對應的描述信息塊就從Free鏈表移除了。

image.png

緩存頁哈希表

有些數據頁被加載到 Buffer Pool 的緩存頁中了,那怎麼知道一個數據頁有沒有被緩存呢?

因此InnoDB還會有一個哈希表數據結構,它用 表空間號+數據頁號 做key,value 就是緩存頁的地址。

當使用一個數據頁的時候,會先經過表空間號+數據頁號做爲key去這個哈希表裏查一下,若是沒有就從磁盤讀取數據頁,若是已經有了,就直接使用該緩存頁。

LRU 鏈表

前面已經知道 Free鏈表管理着全部空閒的緩存頁,當使用了一個緩存頁後,那個緩存頁就從 Free鏈表 移除了,那它對應的控制塊到哪裏去了呢?

這時就有另一個 LRU 鏈表 來管理已經使用了的緩存頁。LRU鏈表與 Free鏈表的結構是相似的,都會有一個基礎節點來指向鏈表的首、尾描述信息塊,加入LRU鏈表中的描述信息塊就經過 free_prefree_next 兩個指針鏈接起來行程一個雙向鏈表。

例如查詢數據的時候,首先從索引找到數據所在的數據頁,再根據 表空間號+頁號 去緩存頁哈希表查找頁是否已經在 Buffer Pool 中了,若是在就直接使用,不在就從磁盤加載頁,而後從 Free鏈表 取出一個空閒頁加入到 LRU鏈表,再把數據頁放到緩存頁中,並以表空間號+頁號做爲 key,緩存頁地址做爲 value,放入緩存頁哈希表。

能夠經過下圖來理解 Free鏈表 和 LRU鏈表,注意緩存頁沒有畫出,描述信息塊是保存了緩存頁的地址的。

image.png

簡單 LRU 鏈表

LRU 鏈表,就是所謂的 Least Recently Used,最近最少使用的意思。由於緩衝池大小是有限的,不可能一直加載數據到緩衝池中,對於一些頻繁訪問的數據能夠一直留在緩衝池中,而一些不多訪問的數據,當緩存頁快用完了的時候,就能夠淘汰掉一些。這時就可使用 LRU鏈表 來管理已使用的緩存頁,這樣就能夠知道哪些頁最常使用,哪些頁最少使用了。

按照正常對LRU鏈表的理解,一個新的數據頁從磁盤加載到緩存頁時,對應的描述信息塊應該是放到 LRU鏈表 的頭部,每次查詢、修改了一個頁,也將這個頁對應的描述信息塊移到 LRU鏈表 的頭部,也就是說最近被訪問過的緩存頁都在 LRU 鏈表的頭部。而後當 Free鏈表 用完了以後,就能夠從 LRU鏈表的尾部找一些最少使用的頁刷入磁盤,騰出一些空閒頁來。

可是這種 LRU鏈表 有幾個問題:

一、InnoDB 有一個預讀機制,就是從磁盤上加載一個數據頁的時候,可能連帶着把這個數據頁相鄰的其它數據頁都加載到緩存裏去。雖然預讀了其它頁,但可能都沒用上,可是這些頁若是都往 LRU 頭部放,就會致使本來常常訪問的頁日後移,而後被淘汰掉。這種狀況屬於加載到 Buffer Pool 中的頁不必定被用到致使緩存命中率下降。

二、若是咱們寫了一個全表掃描的查詢語句,一下就將整個表的頁加載到了 LRU 的頭部,若是表記錄不少的話,可能 LRU 鏈表中以前常常被訪問的頁一下就淘汰了不少,而留下來的數據可能並不會被常常訪問到。這種就是加載了大量使用頻率很低的頁到 Buffer Pool,而後淘汰掉使用頻率很高的頁,從而致使緩存命中率下降。

緩存命中率:假設一共訪問了n次頁,那麼被訪問的頁已經在緩存中的次數除以n就是所謂的緩存命中率,緩存命中率確定是越高越好

冷熱數據分離的 LRU 鏈表

爲了解決簡單 LRU 鏈表的問題,InnoDB在設計 LRU 鏈表的時候,其實是採起冷熱數據分離的思想,LRU鏈表會被拆成兩部分,一部分是熱數據(又稱new列表),一部分是冷數據(又稱old列表)。以下圖所示。

image.png

這個冷熱數據的位置並不固定,是一個比例,由參數 innodb_old_blocks_pct 來控制,默認比例是 37,也就是冷數據佔 37%,大約佔 LRU鏈表 3/8 的樣子。

mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_pct';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_old_blocks_pct | 37    |
+-----------------------+-------+
複製代碼

基於冷熱分離的LRU鏈表,這時新加載一個緩存頁時,就不是直接放到LRU的頭部了,而是放到冷數據區域的頭部。那何時將冷數據區域的頁移到熱數據區域呢?也許你會認爲訪問了一次或幾回頁就會移到熱數據區域的頭部,其實不是這樣的。

若是是預讀機制加載了一些不用的頁,慢慢的被淘汰掉就好了。但若是是全表掃描加載了大量的頁進來,必然是會被讀取至少一次的,並且一頁包含不少條記錄,可能會被訪問屢次。因此這時就將冷數據區域的頁移到熱數據區域也是不太合理的。

因此 InnoDB 設置了一個規則,在第一次訪問冷數據區域的緩存頁的時候,就在它對應的描述信息塊中記錄第一次訪問的時間,默認要間隔1秒後再訪問這個頁,纔會被移到熱數據區域的頭部。也就是從第一次加載到冷數據區域後,1秒內屢次訪問都不會移動到熱數據區域,基本上全表掃描查詢緩存頁的操做1秒內就結束了。

這個間隔時間是由參數 innodb_old_blocks_time 控制的,默認是 1000毫秒。若是咱們把這個參數值設置爲0,那麼每次訪問一個頁面時就會把該頁面放到熱數據區域的頭部。

mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_time';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| innodb_old_blocks_time | 1000  |
+------------------------+-------+
複製代碼

以後緩存頁不夠用的時候,就會優先從冷數據區域的尾部淘汰掉一些不經常使用的頁,頻繁訪問的數據頁仍是會留在熱數據區域,不會受到影響。而冷數據區域停留超過1秒的頁,被再次訪問時就會移到熱數據區域的頭部。

熱數據區域中的頁是每訪問一次就移到頭部嗎?也不是的,熱數據區域是最頻繁訪問的數據,若是頻繁的對LRU鏈表進行節點移動操做也是不合理的。因此 InnoDB 就規定只有在訪問了熱數據區域的 後3/4 的緩存頁纔會被移動到鏈表頭部,訪問 前1/4 中的緩存頁是不會移動的。

下面對冷熱數據分離的LRU鏈表總結下:

  • LRU鏈表分爲冷、熱數據區域,前 63% 爲熱數據區域,後 37% 爲冷數據區域,加載緩存頁先放到冷數據區域頭部。
  • 冷數據區域的緩存頁第一次訪問超過1秒後,再次訪問時纔會被移動到熱數據區域頭部。
  • 熱數據區域中,只有後 3/4 的緩存頁被訪問纔會移到頭部,前 1/4 被訪問到不會移動。
  • 淘汰數據優先淘汰冷數據區域尾部的緩存頁。

Flush 鏈表

當咱們執行增刪改的時候,確定是去更新了 Buffer Pool 中的某些緩存頁,那這些被更新了的緩存頁就和磁盤上的數據頁不一致了,就變成了髒頁。這些髒頁最終確定會被刷回到磁盤中,但並非全部的緩存頁都須要刷回到磁盤,由於有些頁只是被查詢了,但並無被增刪改過。

那怎麼知道哪些頁是髒頁呢?這時就引入了另外一個鏈表,Flush 鏈表。Flush鏈表 跟前面兩個鏈表同樣,也有一個基礎節點,若是一個緩存頁被修改了,就會加入到 Flush鏈表 中。可是不像 LRU鏈表 是從 Free鏈表 中來的,描述信息塊中還有兩個指針 flush_preflush_next用來鏈接造成 flush 鏈表,因此 Flush鏈表 中的緩存頁必定是在 LRU 鏈表中的,而 LRU 鏈表中不在 Flush鏈表 中的緩存頁就是未修改過的頁。能夠經過下圖來理解 LRU 鏈表和 Flsuh鏈表。

能夠看到,髒頁既存在於 LRU鏈表 中,也存在於 Flush鏈表 中。LRU鏈表 用來管理 Buffer Pool 中頁的可用性,Flush鏈表 用來管理將頁刷新回磁盤,兩者互不影響。

image.png

刷新髒頁到磁盤

前面咱們已經知道,LRU鏈表分爲冷熱數據區域,這樣就能夠在空閒緩存頁不夠的用的時候,能夠將LRU鏈表尾部的磁盤頁刷回磁盤,騰出一些空閒頁來,還有 Flush鏈表 中的髒頁,在某些時刻也會刷回磁盤中。那將髒頁刷回磁盤的時機有哪些呢?

  • 定時從 LRU鏈表 尾部刷新一部分髒頁到磁盤

後臺有專門的線程會定時從LRU鏈表尾部掃描一些緩存頁,掃描的數量能夠經過參數 innodb_lru_scan_depth 來設置。若是有髒頁,就會把它們刷回磁盤,而後釋放掉,不是髒頁就直接釋放掉,再把它們加回Free鏈表中。這種刷新頁面的方式被稱之爲 BUF_FLUSH_LRU

  • 定時把 Flush鏈表 中的一些髒頁刷回磁盤

後臺線程會在MySQL不怎麼繁忙的時候,將 Flush 鏈表中的一些髒頁刷到磁盤,這樣LRU熱數據區域的一些髒頁就會被刷回磁盤。這種刷新頁面的方式被稱之爲 BUF_FLUSH_LIST

  • 沒有空閒頁的時候刷新頁

前面兩種方式是後臺線程定時運行,並非在緩存頁滿的時候纔去刷新髒頁,這種方式不會影響用戶線程處理正常的請求。

但可能要加載一個數據頁到 Buffer Pool 時,沒有空閒頁了,這時就會從 LRU鏈表 尾部找一個緩存頁,若是是髒頁就刷回磁盤,若是不是髒頁就釋放掉,而後放入Free鏈表中,再將數據頁放入這個騰出來的空閒頁中。若是要刷新髒頁,這時就會下降處理用戶請求的速度,畢竟和磁盤交互是很慢的。這種刷新單個頁面到磁盤中的刷新方式被稱之爲 BUF_FLUSH_SINGLE_PAGE

查看 Buffer Pool 狀態

咱們能夠經過 SHOW ENGINE INNODE STATUS; 來查看 InnoDB 的狀態信息。可是要注意,狀態並非當前的狀態,而是過去某個時間範圍內 InnoDB 存儲引擎的狀態,例如從下面一次輸出中能夠看到是過去15秒內的一個狀態。

=====================================
2021-04-30 09:20:24 0x7f39a34a7700 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 15 seconds
複製代碼

Buffer Pool和內存狀態

從輸出的內容中,能夠找到 BUFFER POOL AND MEMORY 這段關於緩衝池和內存的狀態信息。

----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 1099431936
Dictionary memory allocated 8281957
Buffer pool size   65535
Free buffers       1029
Database pages     63508
Old database pages 23423
Modified db pages  80
Pending reads      0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 15278983, not young 2027514654
0.00 youngs/s, 0.00 non-youngs/s
Pages read 83326150, created 1809368, written 21840503
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
Buffer pool hit rate 1000 / 1000, young-making rate 1 / 1000 not 0 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 63508, unzip_LRU len: 0
I/O sum[833]:cur[0], unzip sum[0]:cur[0]
複製代碼

其中信息以下:

  • Total large memory allocated:Buffer Pool向操做系統申請的內存空間大小,包括所有控制塊、緩存頁、以及碎片的大小。

  • Dictionary memory allocated:爲數據字典信息分配的內存空間大小,這個內存空間和Buffer Pool沒啥關係,不包括在Total large memory allocated中。

  • Buffer pool size:緩存池頁的數量,因此緩衝池大小爲 65535 * 16KB = 1G

  • Free buffers:Free鏈表中的空閒緩存頁數量。

  • Database pages:LRU鏈表中的緩存頁數量。須要注意的是,Database pages + Free buffers 可能不等於 Buffer pool size,由於緩衝池中的頁還可能分配給自適應哈希索引、Lock信息、Insert Buffer等,而這部分不須要LRU來管理。

  • Old database pages:LRU冷數據區域(old列表)的緩存頁數量,23423/63508=36.88%,約等於 37%

  • Modified db pages:修改過的頁,這就是 Flush鏈表中的髒頁數量。

  • Pending reads:正在等待從磁盤上加載到Buffer Pool中的頁面數量。當準備從磁盤中加載某個頁面時,會先爲這個頁面在Buffer Pool中分配一個緩存頁以及它對應的控制塊,而後把這個控制塊添加到LRU的冷數據區域的頭部,可是這個時候真正的磁盤頁並無被加載進來,因此 Pending reads 的值會加1。

  • Pending writes:從LRU鏈表中刷新到磁盤中的頁面數量,其實就對應着前面說的三種刷盤的時機:BUF_FLUSH_LRU、BUF_FLUSH_LIST、BUF_FLUSH_SINGLE_PAGE

  • Pages made young:顯示了頁從LRU的冷數據區域移到熱數據區域頭部的次數。注意若是是熱數據區域後3/4被訪問移動到頭部是不會增長這個值的。

  • Pages made not young:這個是因爲 innodb_old_blocks_time 的設置致使頁沒有從冷數據區域移到熱數據區域的頁數,能夠看到這個值減小了不少不經常使用的頁被移到熱數據區域。

  • xx youngs/s, xx non-youngs/s:表示 made young 和 not young 這兩類每秒的操做次數。

  • xx reads/s, xx creates/s, xx writes/s:表明讀取,建立,寫入的速率。

  • Buffer pool hit rate xx/1000:表示在過去某段時間,平均訪問1000次頁面,有多少次該頁面已經被緩存到Buffer Pool了,表示緩存命中率。這裏顯示的就是 100%,說明緩衝池運行良好。這是一個重要的觀察變量,一般該值不該該小於 95%,不然咱們應該看下是否有全表掃描引發LRU鏈表被污染的問題。

  • young-making rate xx/1000 not xx/1000:表示在過去某段時間,平均訪問1000次頁面,有多少次訪問使頁面移動到熱數據區域的頭部了,以及沒移動的緩存頁數量。

  • LRU len:LRU 鏈表中節點的數量。

  • I/O sum[xx]:cur[xx]:最近50s讀取磁盤頁的總數,如今正在讀取的磁盤頁數量。

查看LRU鏈表信息

咱們還能夠查詢 information_schema 下的 INNODB_BUFFER_PAGE_LRU 來觀察LRU鏈表中每一個頁的具體信息。

mysql> SELECT * FROM information_schema.INNODB_BUFFER_PAGE_LRU WHERE TABLE_NAME = '`hzero_platform`.`iam_role`';
+---------+--------------+-------+-------------+-----------+------------+-----------+-----------+---------------------+---------------------+-------------+-----------------------------+-------------+----------------+-----------+-----------------+------------+---------+--------+-----------------+
| POOL_ID | LRU_POSITION | SPACE | PAGE_NUMBER | PAGE_TYPE | FLUSH_TYPE | FIX_COUNT | IS_HASHED | NEWEST_MODIFICATION | OLDEST_MODIFICATION | ACCESS_TIME | TABLE_NAME                  | INDEX_NAME  | NUMBER_RECORDS | DATA_SIZE | COMPRESSED_SIZE | COMPRESSED | IO_FIX  | IS_OLD | FREE_PAGE_CLOCK |
+---------+--------------+-------+-------------+-----------+------------+-----------+-----------+---------------------+---------------------+-------------+-----------------------------+-------------+----------------+-----------+-----------------+------------+---------+--------+-----------------+
|       0 |         7938 | 14261 |          66 | INDEX     |          1 |         0 | NO        |        287769805005 |                   0 |  1178342949 | `hzero_platform`.`iam_role` | PRIMARY     |             57 |     14956 |               0 | NO         | IO_NONE | YES    |               0 |
|       0 |         7973 | 14261 |          39 | INDEX     |          1 |         0 | YES       |        287769773866 |                   0 |  1036433373 | `hzero_platform`.`iam_role` | PRIMARY     |             48 |     15102 |               0 | NO         | IO_NONE | YES    |        85389982 |
|       0 |         7974 | 14261 |          12 | INDEX     |          1 |         0 | YES       |        287769585186 |                   0 |  1176755165 | `hzero_platform`.`iam_role` | PRIMARY     |             55 |     15036 |               0 | NO         | IO_NONE | YES    |        85389982 |
|       0 |         7975 | 14261 |          65 | INDEX     |          1 |         0 | YES       |        287769796826 |                   0 |  1176755230 | `hzero_platform`.`iam_role` | PRIMARY     |             52 |     15097 |               0 | NO         | IO_NONE | YES    |        85389982 |
|       0 |         8034 | 14261 |           9 | INDEX     |          1 |         0 | NO        |        287769569861 |                   0 |  1176811763 | `hzero_platform`.`iam_role` | PRIMARY     |             52 |     14958 |               0 | NO         | IO_NONE | YES    |        85389982 |
|       0 |         8035 | 14261 |          11 | INDEX     |          1 |         0 | NO        |        287769577285 |                   0 |  1177589383 | `hzero_platform`.`iam_role` | PRIMARY     |             52 |     15040 |               0 | NO         | IO_NONE | YES    |        85389982 |
|       0 |         8036 | 14261 |          13 | INDEX     |          1 |         0 | NO        |        287769592932 |                   0 |  1177589384 | `hzero_platform`.`iam_role` | PRIMARY     |             54 |     15011 |               0 | NO         | IO_NONE | YES    |        85389982 |
|       0 |         8037 | 14261 |          14 | INDEX     |          1 |         0 | NO        |        287769599951 |                   0 |  1177589384 | `hzero_platform`.`iam_role` | PRIMARY     |             49 |     15170 |               0 | NO         | IO_NONE | YES    |        85389982 |
複製代碼

其中的一些信息以下:

  • POOL_ID:緩衝池ID,咱們是能夠設置多個緩衝池的。

  • SPACE:頁所屬表空間ID,表空間ID也能夠從 information_schema.INNODB_SYS_TABLES 去查看。

  • PAGE_NUMBER:頁號。

  • PAGE_TYPE:頁類型,INDEX就是數據頁。

  • NEWEST_MODIFICATION、OLDEST_MODIFICATION:LRU熱數據區域和冷數據區域被修改的記錄,若是想查詢髒頁的數量,能夠加上條件 (NEWEST_MODIFICATION > 0 or OLDEST_MODIFICATION > 0)

  • NUMBER_RECORDS:這一頁中的記錄數。

  • COMPRESSED:是否壓縮了。

INNODB_SYS_TABLES查看錶信息:

mysql> SELECT * FROM information_schema.INNODB_SYS_TABLES WHERE NAME = 'hzero_platform/iam_role';
+----------+-------------------------+------+--------+-------+-------------+------------+---------------+------------+
| TABLE_ID | NAME                    | FLAG | N_COLS | SPACE | FILE_FORMAT | ROW_FORMAT | ZIP_PAGE_SIZE | SPACE_TYPE |
+----------+-------------------------+------+--------+-------+-------------+------------+---------------+------------+
|    14552 | hzero_platform/iam_role |   33 |     30 | 14261 | Barracuda   | Dynamic    |             0 | Single     |
+----------+-------------------------+------+--------+-------+-------------+------------+---------------+------------+
複製代碼

Buffer Pool 配置調優

配置多個Buffer Pool來提高數據庫的併發性能

多線程訪問 Buffer Pool 的時候,會涉及到對同一個 Free、LRU、Flush 等鏈表的操做,例如節點的移動、緩存頁的刷新等,那必然是會涉及到加鎖的。

首先要知道,就算只有一個 Buffer Pool,多線程訪問要加鎖、釋放鎖,因爲基本都是內存操做,因此性能也是很高的。但在一些高併發的生產環境中,配置多個 Buffer Pool,仍是能極大地提升數據庫併發性能的。

能夠經過參數 innodb_buffer_pool_instances 來配置 Buffer Pool 實例數,經過參數 innodb_buffer_pool_size 設置全部 Buffer Pool 的總大小(單位字節)。每一個 Buffer Pool 的大小就是 innodb_buffer_pool_size / innodb_buffer_pool_instances

[server]
innodb_buffer_pool_size=2147483648
innodb_buffer_pool_instances=2
複製代碼

InnoDB 規定,當 innodb_buffer_pool_size 小於1GB的時候,設置多個實例是無效的,會默認把innodb_buffer_pool_instances 的值修改成1

動態調整Buffer Pool大小

咱們能夠在運行時動態調整 innodb_buffer_pool_size 這個參數,但 InnoDB 並非一次性申請 pool_size 大小的內存空間,而是以 chunk 爲單位申請。一個 chunk 默認就是 128M,表明一片連續的空間,申請到這片內存空間後,就會被分爲若干緩存頁與其對應的描述信息塊。

也就是說一個Buffer Pool實例實際上是由若干個chunk組成的,每一個chunk裏劃分了描述信息塊和緩存頁,而後共用一套 Free鏈表、LRU鏈表、Flush鏈表。

image.png

每一個chunk 的大小由參數 innodb_buffer_pool_chunk_size 控制,這個參數只能在服務器啓動時指定,不能在運行時動態修改。

mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_chunk_size';
+-------------------------------+-----------+
| Variable_name                 | Value     |
+-------------------------------+-----------+
| innodb_buffer_pool_chunk_size | 134217728 |
+-------------------------------+-----------+
複製代碼

合理設置 Buffer Pool 大小

在生產環境中安裝MySQL數據庫,首先咱們通常要選擇大內存的機器,那咱們如何合理的設置 Buffer Pool 的大小呢?

好比有一臺 32GB 的機器,不可能說直接給個30G,要考慮幾個方面。首先前面說過,innodb_buffer_pool_size 並不包含描述塊的大小,實際 Buffer Pool 的大小會超出 innodb_buffer_pool_size 5% 左右。另外機器自己運行、MySQL運行也會佔用必定的內存,因此通常 Buffer Pool 能夠設置爲機器的 50%~60% 左右就能夠了,好比32GB的機器,就設置 innodb_buffer_pool_size 爲 20GB。

另外,innodb_buffer_pool_size 必須是 innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances 的倍數,主要是保證每個Buffer Pool實例中包含的chunk數量相同。

好比默認 chunk_size=128MB,pool_size 設置 20GB,pool_instances 設置 16 個,那麼 20GB / (128MB * 16) = 10 倍,這樣每一個 Buffer Pool 的大小就是 128MB * 10 = 1280MB。若是將 pool_instances 設置爲 32 個,那麼 20GB / (128MB * 32) = 5 倍,這樣每一個 Buffer Pool 的代銷就是 128MB * 5 = 640MB

相關文章
相關標籤/搜索