1. 程式人生 > >InnoDB日誌管理機制(一) – 運維派

InnoDB日誌管理機制(一) – 運維派

引 子

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的背景和實現開始講起。

Buffer Poo

仔細說說InnoDB Buffer Pool

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各自管理自己的記憶體和連結串列,可以提升效率。

Buffer Pool

Buffer Pool實現原理

在啟動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