系列文章:MySQL系列專欄mysql
經過前面幾篇文章,咱們已經瞭解到 InnoDB 存儲引擎是基於磁盤存儲的,並將其中的記錄按照頁的方式進行管理。但因爲CPU速度與磁盤速度之間的鴻溝,一般都會將數據加載到內存中來操做數據,以此提升數據庫的總體性能。web
InnoDB 設計了一個緩衝池(Buffer Pool),當須要訪問某個頁時,就會把這一頁的數據所有加載到緩衝池中,這樣就能夠在內存中進行讀寫訪問了。對於數據庫中頁的修改操做,也是先修改在緩衝池中的頁,而後再以必定的頻率刷新到磁盤上。有了緩衝池,就能夠省去不少磁盤IO的開銷了,從而提高數據庫性能。sql
注意即便只訪問頁中的一條記錄,也須要把整個頁的數據加載到內存中。前面有說過,經過索引只能定位到磁盤中的頁,而不能定位到頁中的一條記錄。將頁加載到內存後,就能夠經過頁目錄(Page Directory)去定位到某條具體的記錄。數據庫
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 中,每一個緩存頁的描述信息放在最前面,各個緩存頁在後面。看起來就像下圖這個樣子。多線程
另外須要注意下,每一個描述數據大約至關於緩存頁大小的 5%
,也就是800字節
左右的樣子。而咱們設置的 innodb_buffer_pool_size
並不包含描述數據的大小,實際上 Buffer Pool 的大小會超出這個值。好比默認配置 128MB,那麼InnoDB在爲 Buffer Pool 申請連續的內存空間時,會申請差很少 128 + 128*5% ≈ 134MB
大小的空間。併發
Buffer Pool 中劃分緩存頁的時候,會讓全部的緩存頁和描述數據塊都緊密的挨在一塊兒,前面是描述數據塊,後面是緩存頁,儘量減小內存浪費。可是劃分完後,可能還剩一點點的內存,這點內存放不下任何一個緩存頁了,這就是圖中的碎片空間了。
MySQL 數據庫啓動的時候,InnoDB 就會爲 Buffer Pool 申請一片連續的內存空間,而後按照默認的16KB
的大小劃分出一個個的頁來,還會按照800字節
左右的大小劃分出頁對應的描述數據來。
劃分完成後,Buffer Pool 中的緩存頁都是空的,等執行增刪改查等操做時,纔會把數據對應的頁從磁盤加載到 Buffer Pool 中的頁來。
那怎麼知道哪些頁是空閒的呢?InnoDB 設計了一個 free 鏈表
,它是一個雙向鏈表數據結構,這個鏈表的每一個節點就是一個空閒緩存頁的描述信息。
實際上,每一個描述信息中有 free_pre
、free_next
兩個指針,Free鏈表 就是由這兩個指針鏈接起來造成的一個雙向鏈表。而後 Free鏈表 有一個基礎節點,這個基礎節點存放了鏈表的頭節點地址、尾節點地址,以及當前鏈表中節點數的信息。
Free鏈表 看起來就像下圖這樣:
須要注意的是,鏈表的基礎節點佔用的內存空間並不包含在 Buffer Pool 以內,而是單獨申請的一塊內存空間,每一個基節點只佔用40字節
大小。後邊介紹的其它鏈表的基礎節點也是同樣的,都是單獨申請的一塊40字節大小的內存空間。
有了這個 Free鏈表 以後,當須要從磁盤加載一個頁到 Buffer Pool 時,就從 Free鏈表 中取出一個描述數據塊,而後將頁寫入這個描述數據塊對應的空閒緩存頁中。並把一些描述數據寫入描述數據塊中,好比頁的表空間號、頁號之類的。最後,把緩存頁對應的描述數據塊從 Free鏈表 中移除,表示該緩存頁已被使用了。
能夠看到,從磁盤加載一個頁到緩存頁後,緩存頁對應的描述信息塊就從Free鏈表移除了。
有些數據頁被加載到 Buffer Pool 的緩存頁中了,那怎麼知道一個數據頁有沒有被緩存呢?
因此InnoDB還會有一個哈希表數據結構,它用 表空間號+數據頁號
做key,value 就是緩存頁的地址。
當使用一個數據頁的時候,會先經過表空間號+數據頁號
做爲key去這個哈希表裏查一下,若是沒有就從磁盤讀取數據頁,若是已經有了,就直接使用該緩存頁。
前面已經知道 Free鏈表
管理着全部空閒的緩存頁,當使用了一個緩存頁後,那個緩存頁就從 Free鏈表 移除了,那它對應的控制塊到哪裏去了呢?
這時就有另一個 LRU 鏈表
來管理已經使用了的緩存頁。LRU鏈表與 Free鏈表的結構是相似的,都會有一個基礎節點來指向鏈表的首、尾描述信息塊,加入LRU鏈表中的描述信息塊就經過 free_pre
和 free_next
兩個指針鏈接起來行程一個雙向鏈表。
例如查詢數據的時候,首先從索引找到數據所在的數據頁,再根據 表空間號+頁號 去緩存頁哈希表查找頁是否已經在 Buffer Pool 中了,若是在就直接使用,不在就從磁盤加載頁,而後從 Free鏈表 取出一個空閒頁加入到 LRU鏈表,再把數據頁放到緩存頁中,並以表空間號+頁號做爲 key,緩存頁地址做爲 value,放入緩存頁哈希表。
能夠經過下圖來理解 Free鏈表 和 LRU鏈表,注意緩存頁沒有畫出,描述信息塊是保存了緩存頁的地址的。
LRU 鏈表
,就是所謂的 Least Recently Used
,最近最少使用的意思。由於緩衝池大小是有限的,不可能一直加載數據到緩衝池中,對於一些頻繁訪問的數據能夠一直留在緩衝池中,而一些不多訪問的數據,當緩存頁快用完了的時候,就能夠淘汰掉一些。這時就可使用 LRU鏈表 來管理已使用的緩存頁,這樣就能夠知道哪些頁最常使用,哪些頁最少使用了。
按照正常對LRU鏈表的理解,一個新的數據頁從磁盤加載到緩存頁時,對應的描述信息塊應該是放到 LRU鏈表 的頭部,每次查詢、修改了一個頁,也將這個頁對應的描述信息塊移到 LRU鏈表 的頭部,也就是說最近被訪問過的緩存頁都在 LRU 鏈表的頭部。而後當 Free鏈表 用完了以後,就能夠從 LRU鏈表的尾部找一些最少使用的頁刷入磁盤,騰出一些空閒頁來。
可是這種 LRU鏈表 有幾個問題:
一、InnoDB 有一個預讀機制
,就是從磁盤上加載一個數據頁的時候,可能連帶着把這個數據頁相鄰的其它數據頁都加載到緩存裏去。雖然預讀了其它頁,但可能都沒用上,可是這些頁若是都往 LRU 頭部放,就會致使本來常常訪問的頁日後移,而後被淘汰掉。這種狀況屬於加載到 Buffer Pool 中的頁不必定被用到致使緩存命中率下降。
二、若是咱們寫了一個全表掃描
的查詢語句,一下就將整個表的頁加載到了 LRU 的頭部,若是表記錄不少的話,可能 LRU 鏈表中以前常常被訪問的頁一下就淘汰了不少,而留下來的數據可能並不會被常常訪問到。這種就是加載了大量使用頻率很低的頁到 Buffer Pool,而後淘汰掉使用頻率很高的頁,從而致使緩存命中率下降。
緩存命中率:假設一共訪問了n次頁,那麼被訪問的頁已經在緩存中的次數除以n就是所謂的緩存命中率,緩存命中率確定是越高越好
爲了解決簡單 LRU 鏈表的問題,InnoDB在設計 LRU 鏈表的時候,其實是採起冷熱數據分離
的思想,LRU鏈表會被拆成兩部分,一部分是熱數據
(又稱new列表
),一部分是冷數據
(又稱old列表
)。以下圖所示。
這個冷熱數據的位置並不固定,是一個比例,由參數 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鏈表總結下:
63%
爲熱數據區域,後 37%
爲冷數據區域,加載緩存頁先放到冷數據區域頭部。3/4
的緩存頁被訪問纔會移到頭部,前 1/4
被訪問到不會移動。當咱們執行增刪改的時候,確定是去更新了 Buffer Pool 中的某些緩存頁,那這些被更新了的緩存頁就和磁盤上的數據頁不一致了,就變成了髒頁。這些髒頁最終確定會被刷回到磁盤中,但並非全部的緩存頁都須要刷回到磁盤,由於有些頁只是被查詢了,但並無被增刪改過。
那怎麼知道哪些頁是髒頁呢?這時就引入了另外一個鏈表,Flush 鏈表
。Flush鏈表 跟前面兩個鏈表同樣,也有一個基礎節點,若是一個緩存頁被修改了,就會加入到 Flush鏈表 中。可是不像 LRU鏈表 是從 Free鏈表 中來的,描述信息塊中還有兩個指針 flush_pre
、flush_next
用來鏈接造成 flush 鏈表,因此 Flush鏈表 中的緩存頁必定是在 LRU 鏈表中的,而 LRU 鏈表中不在 Flush鏈表 中的緩存頁就是未修改過的頁。能夠經過下圖來理解 LRU 鏈表和 Flsuh鏈表。
能夠看到,髒頁既存在於 LRU鏈表 中,也存在於 Flush鏈表 中。LRU鏈表 用來管理 Buffer Pool 中頁的可用性,Flush鏈表 用來管理將頁刷新回磁盤,兩者互不影響。
前面咱們已經知道,LRU鏈表分爲冷熱數據區域,這樣就能夠在空閒緩存頁不夠的用的時候,能夠將LRU鏈表尾部的磁盤頁刷回磁盤,騰出一些空閒頁來,還有 Flush鏈表 中的髒頁,在某些時刻也會刷回磁盤中。那將髒頁刷回磁盤的時機有哪些呢?
後臺有專門的線程會定時從LRU鏈表
尾部掃描一些緩存頁,掃描的數量能夠經過參數 innodb_lru_scan_depth
來設置。若是有髒頁,就會把它們刷回磁盤,而後釋放掉,不是髒頁就直接釋放掉,再把它們加回Free鏈表
中。這種刷新頁面的方式被稱之爲 BUF_FLUSH_LRU
。
後臺線程會在MySQL不怎麼繁忙的時候,將 Flush 鏈表中的一些髒頁刷到磁盤,這樣LRU熱數據區域的一些髒頁就會被刷回磁盤。這種刷新頁面的方式被稱之爲 BUF_FLUSH_LIST
。
前面兩種方式是後臺線程定時運行,並非在緩存頁滿的時候纔去刷新髒頁,這種方式不會影響用戶線程處理正常的請求。
但可能要加載一個數據頁到 Buffer Pool 時,沒有空閒頁了,這時就會從 LRU鏈表 尾部找一個緩存頁,若是是髒頁就刷回磁盤,若是不是髒頁就釋放掉,而後放入Free鏈表中,再將數據頁放入這個騰出來的空閒頁中。若是要刷新髒頁,這時就會下降處理用戶請求的速度,畢竟和磁盤交互是很慢的。這種刷新單個頁面到磁盤中的刷新方式被稱之爲 BUF_FLUSH_SINGLE_PAGE
。
咱們能夠經過 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 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讀取磁盤頁的總數,如今正在讀取的磁盤頁數量。
咱們還能夠查詢 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 的時候,會涉及到對同一個 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
。
咱們能夠在運行時動態調整 innodb_buffer_pool_size
這個參數,但 InnoDB 並非一次性申請 pool_size 大小的內存空間,而是以 chunk
爲單位申請。一個 chunk 默認就是 128M
,表明一片連續的空間,申請到這片內存空間後,就會被分爲若干緩存頁與其對應的描述信息塊。
也就是說一個Buffer Pool實例實際上是由若干個chunk
組成的,每一個chunk裏劃分了描述信息塊和緩存頁,而後共用一套 Free鏈表、LRU鏈表、Flush鏈表。
每一個chunk
的大小由參數 innodb_buffer_pool_chunk_size
控制,這個參數只能在服務器啓動時指定,不能在運行時動態修改。
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_chunk_size';
+-------------------------------+-----------+
| Variable_name | Value |
+-------------------------------+-----------+
| innodb_buffer_pool_chunk_size | 134217728 |
+-------------------------------+-----------+
複製代碼
在生產環境中安裝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