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