《《MySQL運維內參》節選》要點:
本文介紹了《MySQL運維內參》節選,希望對您有用。如果有疑問,可以聯系我們。
InnoDB 存儲引擎是支持事務ACID特性的,它是以二十多年前IBM的一篇著名文章《ARIES:A Transaction Recovery Method Supporting Fine-Granularity Locking and PartialRollbacks Using Write-Ahead Logging》為理論基礎,大多數關系型數據庫的實現都是基于這個理論的,包括Oracle、DM等.
這個理論基本就是一個關系型數據庫相關的數據庫恢復原型設計,包括日志、回滾、REDO、并發控制、BufferPool管理等方面,內容非常全面.同時,這些內容是一個不可分割的整體,它們的共同目標之一就是保證數據庫數據的一致性,保證數據庫事務的ACID特性,所以這一節要講的東西都是互相遷連的,它們之間相互作用,相互配合,相互驅動,才能保證數據庫的數據完整性.下面就先從Buffer Pool的背景和實現開始講起.
InnoDB的Buffer Pool主要用來存儲訪問過的數據頁面,它就是一塊連續的內存,通過一定的算法可以使這塊內存得到有效的管理.它是數據庫系統中擁有最大塊內存的系統模塊.
InnoDB存儲引擎中數據的訪問是按照頁(有的也叫塊,默認為16K)的方式從數據庫文件讀取到Buffer Pool中的,然后在內存中用同樣大小的內存空間來做一個映射.為了提高數據訪問效率,數據庫系統預先就分配了很多這樣的空間,用來與文件中的數據進行交換.訪問時按照最近最少使用(LRU)算法來實現Buffer Pool頁面的管理,經常訪問的頁面在最前面,最不經常的頁面在最后面.如果Buffer Pool中沒有空閑的頁面來做文件數據的映射,就找到Buffer Pool中最后面且不使用的位置,將其淘汰,然后用來映射新數據文件頁面,同時將它移到LRU鏈表中的最前面.這樣就能保證經常訪問的頁面在沒有刷盤的情況下始終在Buffer Pool中,從而保證了數據庫的訪問效率.
Buffer Pool的大小可以在配置文件中配置,由參數innodb_buffer_pool_size的大小來決定,默認大小為128M.在MySQL 5.7.4之前,一旦MySQL已經啟動,這個值便不能再做修改,如果需要修改,只能退出MySQL進程,然后修改對應的配置文件來設置新的Buffer Pool大小,重新啟動后才能生效.這在運維上非常不方便,因為很多時候,需要去調整Buffer Pool的大小,特別是在單機多實例,或者提供云數據庫服務的情況下,我們需要根據用戶及實際業務的需要,不斷地去動態增加或減少Buffer Pool size,從而合理地利用內存及優化數據庫.
讓人慶幸的是,MySQL官方也發現了這種不便.在MySQL 5.7.5之后,MySQL在源碼上改變了對Buffer Pool的管理,可以在MySQL進程運行的情況下,動態地配置innodb_buffer_pool_size.另外,需要強調的是,如果Buffer Pool的大小超過了1GB,應該通過調整 innodb_buffer_pool_instances=N,把它分成若干個instance的做法,來提升MySQL處理請求的并發能力,因為Buffer Pool是通過鏈表的方式來管理頁面的,同時為了保護頁面,需要在存取的時候對鏈表加鎖,在多線程的情況下,并發去讀寫Buffer Pool里面緩存的頁面需要鎖的競爭和等待.所以,修改為多個instance,每個instance各自管理自己的內存和鏈表,可以提升效率.
在啟動MySQL服務時,會將所有的內嵌存儲引擎啟動,包括InnoDB.InnoDB會通過函數buf_pool_init初始化所有的子系統,其中就包括了InnoDB Buffer Pool子系統.Buffer Pool可以有多個實例,可以通過配置文件中的參數innodb_buffer_pool_instances來設置,默認值為1,實現多實例的Buffer
Pool主要是為了提高數據頁訪問時的并發度.每個實例的空間大小都是相同的,也就是說系統會將整個配置的Buffer Pool大小按實例個數平分,然后每個實例各自進行初始化操作.
在代碼中,一個Buffer Pool實例用buf_pool_t結構體來描述,這個結構體是用來管理Buffer Pool實例的一個核心工具,它包括了很多信息,主要有如下幾個部分.
1. FREE鏈表,用來存儲這個實例中所有空閑的頁面.
2. flush_list鏈表,用來存儲所有被修改過且需要刷到文件中的頁面.
3. mutex,主要用來保護這個Buffer Pool實例,因為一個實例只能由一個線程訪問.
4. chunks,指向這個Buffer ?Pool實例中第一個真正內存頁面的首地址,頁面都是連續存儲,所以通過這個指針就可以直接訪問所有的其他頁面.
上面的兩個鏈表,管理的對象是結構體buf_page_t,這是一個物理頁面在內存中的管理結構,是一個頁面狀態信息的結合體,其中包括所屬表空間、Page
ID、最新及最早被修改的LSN值(最早LSN信息會在做檢查點時使用,后面將會講到),以及形成Page鏈表的指針等邏輯信息.實際上,這個結構是被另一個結構管理的,它是buf_block_t,buf_block_t與buf_page_t是一一對應的,都對應BufferPool中的一個Page,只是buf_page_t是邏輯的,而buf_block_t包含一部分物理的概念,比如這個頁面的首地址指針frame等.關于buf_block_t,后面還會繼續介紹.
初始化一個Buffer Pool實例內存空間的函數是buf_chunk_init.一個Buffer
Pool實例的內存分布是一塊連續的內存空間,這塊內存空間中存儲了兩部分內容,前面是這些數據緩存頁面的控制頭結構信息(buf_block_t結構),每一個控制頭信息管理一個物理頁面,這些控制頭信息的存儲,占用了部分Buffer
Pool空間,所以在運維過程中,看到狀態參數innodb_buffer_pool_bytes_data
總是比innoDB_buffer_pool_size
小,就是因為控制頭信息占用了部分空間.實際的分配方式是,Buffer Pool頁面從整個實例池中從后向前分配,每次分配一個頁面,而控制結構是從前向后分配,每次分配一個buf_block_t結構的大小,直到相遇為止,這樣就將一個實例初始化好了.但一般情況下,中間都會剩余一部分沒有被使用,因為剩余的空間不能再放得下一個控制結構與一個頁面了.相應的分配代碼如下.
其中,`chunk->size`是在前面提前根據Buffer Pool實例內存大小計算出來的,可以存儲的最多的Page及Page對應控制結構的個數.
限于篇幅,本文第一部分結束.
文章來自微信公眾號:DBAce