1. 程式人生 > >MySQL技術內幕InnoDB儲存引擎-02InnoDB儲存引擎

MySQL技術內幕InnoDB儲存引擎-02InnoDB儲存引擎

1、InnoDB儲存引擎概述

從MySQL5.5.8版本開始是預設的表儲存引擎,該儲存引擎是第一個完整支援ACID事務的MySQL儲存引擎,其特點是行鎖設計、支援MVCC、支援外來鍵、提供一致性非鎖定讀,同時被設計用來最有效地利用以及使用記憶體和CPU。

2、InnoDB體系架構

InnoDB儲存引擎有多個記憶體塊,可以認為這些記憶體塊組成了一個大的記憶體池,負責如下工作:
1.維護所有程序/執行緒需要訪問的多個內部資料結構。
2.快取磁碟上的資料,方便快速的讀取,同事在對磁碟檔案的資料修改之前在這裡快取。
3.重做日誌(redo log)緩衝。
這裡寫圖片描述

2.1 後臺執行緒

InnoDB儲存引擎是多執行緒的模型,因此後臺有多個不同的後臺執行緒,負責處理不同任務。

(1)Master Thread
非常核心的後臺執行緒,主要負責將緩衝池中的資料非同步重新整理到磁碟,保證資料的一致性,包括髒頁的重新整理,合併插入緩衝(insert buffer)、undo頁的回收等。

(2)IO Thread
在InnoDB儲存引擎中大量使用了 AIO(Async IO 非同步的) 來處理寫IO請求,這樣可以極大提高資料庫的效能。而IO Thread的工作主要負責這些IO請求的回撥(call back)。

InnoDB 1.0版本之前共有4個IO Thread,分別是: write 、read 、 insert buffer 、log IO thread。

1.檢視InnoDB的版本: show variables like ‘innodb_version’
2.檢視MySQL的版本: select version()
3.檢視read、write的執行緒數量:show variables like ‘innodb_%io_threads’
4.檢視IO Thread情況:show engine innodb status (讀的執行緒id總是小於寫的執行緒)

(3)Purge Thread
事務被提交後,其所使用的undolog可能不再需要,因此需要 PurgeThread來回收已經使用並分配的undo頁

InnoDB 1.1版本以前,這個操作由Master Thread來完成,1.1版本以後該操作可以獨立到單獨的執行緒中進行,可以提高效能。

通過在配置檔案中新增該命令啟動獨立的Purge Thread:innodb_purge_threads=1
1.2版本以後,支援多個 PurgeThread 通過命令檢視數量:show variables like ‘innodb_purg_threads’

(4)Page Cleaner Thread
1.2版本以後引入的,作用是將之前版本中髒頁的重新整理操作都放入單獨的執行緒中完成,目的是為了減輕原Master Thread 的工作及對於使用者查詢執行緒的阻塞。

及對於使用者查詢執行緒的阻塞,進一步提高InnoDB儲存引擎的效能。

2.2 記憶體

(1)緩衝池
InnoDB儲存引擎是基於磁碟儲存的,並將其中的記錄按照頁的方式進行管理,由於CPU和磁碟速度之間的鴻溝,基於磁碟的資料庫系統通常使用緩衝池技術來提高資料庫的整體效能。緩衝池就是一塊記憶體區域,通過記憶體的速度來彌補磁碟速度較慢對資料庫效能的影響。

資料庫中進行讀取頁操作:
1、將從磁碟讀取的頁存放在緩衝池中(將頁FIX在緩衝池中)
2、下次讀同樣的頁時,判斷該頁是否在緩衝池中,如果在則稱為被命中,否則,進行第1步

資料庫中頁的修改操作:
1、修改緩衝池中的頁
2、以一定的頻率重新整理到磁碟上。通過Checkpoint的機制重新整理回磁碟。

緩衝池中快取的而資料頁型別有:索引頁,資料頁,undo頁,插入緩衝(insert buffer),自適應雜湊索引(adaptive hash index),InnoDB儲存的鎖資訊(lock info)、資料字典資訊(data dicttionary)等。除了緩衝池(innodb_buffer_pool)還有重做日誌緩衝(redo log_buffer)和額外記憶體池(innodb_addtional_mem_pool_size)。InnoDB儲存引擎中記憶體結構情況如下:
這裡寫圖片描述

緩衝池的大小通過來 innodb_buffer_pool_size 設定:show variables like ‘innodb_buffer_pool_size’;

從InnoDB 1.0以後,允許有多個緩衝池例項,每個頁根據雜湊值平均分配到不同緩衝池例項中。

可以通過innodb_buffer_pool_instances在配置檔案中進行配置:show variables like ‘innodb_buffer_pool_instances’

MySQL5.6以後 ,可以通過information_schema 架構下的表 innodb_buffer_pool_stats 觀察緩衝的狀態use information_schema;
select pool_id,pool_size,free_buffers,database_pages from innodb_buffer_pool_stats;

(1)緩衝池中記憶體的管理——LRU list,Free list,Flush list
1、LRU list
通常來說,資料庫中的緩衝池是通過LRU(Lastest Recent Used,最近最少使用)演算法進行管理的。即最頻繁使用的頁在LRU列表的前端,而最少使用的頁在LRU列表的尾端,當緩衝池不能存放新讀取的頁時,將首先釋放LRU列表中尾端的頁。

在InnoDB儲存引擎中,緩衝池中頁的大小預設為16KB,最新訪問的頁並不是直接放入到LRU列表的首部,而是放入LRU列表的midpoint位置。這個演算法在InnoDB儲存引擎下稱為 midpoint insertion strategy,預設midpoint是在LRU列表長度的5/8處,midpoint位置可由引數 innodb_old_blocks_pct 控制,預設值為37,表示新讀取的頁插入到LRU列表尾端的37%的位置(差不多3/8的位置)。在InnoDB儲存引擎中,把midpoint之後的列表成為old列表,之前的列表成為new列表,可以簡單的理解為new列表中的頁都是最為活躍的熱點資料。

mysql> show variables like ‘innodb_old_blocks_pct’;
+————————+——-+
| Variable_name | Value |
+————————+——-+
| innodb_old_blocks_pct | 37 |

這樣做的目的:
防止出現偶爾使用的大資料量的讀操作,把緩衝池中大量的甚至全部的頁釋放掉,由於這樣的操作是偶爾進行的,所以讀到的頁並不是活躍的熱點資料,如果放在LRU列表的開頭可能會把真正的熱點資料清除掉,使下一次熱點資料的讀取需要訪問磁碟。

可以通過調小innodb_old_blocks_pct ,如: set global innodb_old_blocks_pct=20 減少熱點頁被刷出的概率。InnoDB儲存引擎引入了另一個引數來進一步管理LRU列表:innodb_old_blocks_time。用於表示頁讀取到mid位置後需要等待多久才會被加入到LRU列表的熱端:show variables like ‘innodb_old_blocks_time’。

2、Free list
LRU列表管理的是已經讀取的頁。當資料庫剛啟動時,LRU列表是空的,即沒有任何的頁,這時的頁都存放在Free列表中。當需要從緩衝池中分頁時:
1、從Free列表中查詢是否有可用的空閒頁,若有則將該頁從Free列表刪除,放入到LRU列表中
2、否則,根據LRU演算法,淘汰LRU列表末尾的頁,將該記憶體空間分配給新的頁

當頁從LRU列表的old部分加入到new部分,稱為:page made young (顯示了LRU列表中移動到前端的次數),因為innodb_old_blocks_time的設定,導致頁沒有從old部分移動到new部分的操作稱為:page not made young。

buffer pool hit rate 緩衝池的命中率,小於95%時需要觀察是否由於全表掃描引起的LRU列表被汙染的問題。

InnoDB 1.2以後,可以通過information_schema 架構下的表 innodb_buffer_pool_stats 觀察緩衝池的狀態
執行。
use information_schema;
select pool_id,pool_size,free_buffers,database_pages from innodb_buffer_pool_stats;
還可以通過表innodb_buffer_page_lru 觀察每個LRU列表中每個頁的具體資訊:
select * from innodb_buffer_page_lru limit 1

InnoDB 1.0以後,開始支援壓縮頁的功能,即將原本16KB的頁壓縮為1KB、2KB、4KB和8KB。對於非16KB的頁,是通過unzip_LRU列表進行管理的。同樣可以通過information_schema架構下的表innodb_buffer_page_lru來觀察unzip_LRU列表中的頁。

3、Flush list
LRU列表中的頁被修改後,該頁稱為髒頁(dirty page),即緩衝池中的頁和磁碟上的頁的資料產生了不一致。這時,資料庫會通過checkpoint機制將髒頁重新整理回磁碟,而Flush列表中的頁即為髒頁列表。注意:髒頁既存在於LRU列表中,也存在於Flush列表中。LRU列表用來管理緩衝池中頁的可用性,FLush列表用來管理將頁重新整理回磁碟,二者互不影響。

因為髒頁同樣存在於LRU列表中,故使用者可以通過源資料表 innodb_buffer_page_lru 來查,需要加入 oldest_modification 大於 0 的查詢條件:
select * from innodb_buffer_page_lru where oldest_modification > 0
通過 show engine innodb status 可以檢視緩衝池中這些列表的資訊,但不是實時的。

(3)重做日誌緩衝

InnoDB儲存引擎記憶體區域除了有緩衝池外,還有重做日誌緩衝———— redo log buffer。InnoDB儲存引擎首先把重做日誌資訊放入這個緩衝區,然後按一定頻率將其重新整理到重做日誌檔案。一般情況下每一秒鐘會將重做日誌緩衝重新整理到日誌檔案,因此使用者只需要保證每秒產生的事務量在這個緩衝大小之內即可。
show variables like ‘innodb_log_buffer_size’ \G 預設為8M
將重做日誌緩衝重新整理到外部磁碟的重做日誌檔案中:
(1) Master Thread 每一秒將重做日誌緩衝重新整理到重做日誌檔案;
(2)每個事務提交時會將重做日誌緩衝重新整理到重做日誌檔案;
(3)當重做日誌緩衝池剩餘空間小於1/2 時。

(4)額外的記憶體池
在InnoDB儲存引擎中,對記憶體的管理是通過一種稱為記憶體堆(heap)的方式進行的。在對一些資料結構本身進行分配時,需要從額外的記憶體池中進行申請。當該區域的記憶體不夠時,會從緩衝池中進行申請。例如:
分配了緩衝池(innodb_buffer_pool),但是每個緩衝池中的幀緩衝(frame buffer)還有對應的緩衝控制物件(buffer control block),這些物件記錄了一些諸如LRU、鎖、等待等資訊,而這個物件的記憶體需要從額外記憶體池中申請。因此,在申請了很大的InnoDB緩衝池時,也應考慮相應地增加這個值。

3.Checkpoint 技術

緩衝池的設計目的是為了協調CPU速度與磁碟速度的鴻溝,為了避免在緩衝池將新版本資料重新整理到磁碟時發生宕機,從而使資料無法恢復,當前事務資料系統普遍採用 Write Ahead Log 策略,即當事務提交時,先寫重做日誌,再修改頁。這樣就可以通過重做日誌來完成資料的恢復,達到事務ACID中D(Durability 永續性)的要求。如此來說要恢復資料需要兩個前提條件:
1、緩衝池可以快取資料庫中所有的資料;
2、重做日誌可以無限增大。

即便是上述兩個條件都滿足,那麼還有一個情況:宕機後資料庫的恢復時間。 (如果是運行了幾個月甚至幾年的資料庫,這是發生宕機,重新應用重做日誌的時間會非常久)。
因此Checkpoint(檢查點)技術的目的是解決以下幾個問題:
1、縮短資料庫的恢復時間;
2、緩衝池不夠用時,將髒頁重新整理到磁碟;
3、重做日誌不可用時,重新整理髒頁。

因此,當資料庫發生宕機,需要恢復資料時,只需要對Checkpoint後的重做日誌進行恢復,因為Checkpoint之前的頁都已經重新整理回磁碟。此外,當緩衝池不夠用時,根據LRU演算法會溢位最近最少使用的頁,若此頁為髒頁,那麼需要強制執行Checkpoint,將髒頁也就是頁的最新版本刷回磁碟。

重做日誌出現不可用的情況:
1、當前事務資料庫系統對重做日誌的設計都是迴圈使用的;
2、重做日誌可以被重用的部分是指資料庫恢復操作不需要這部分的重做日誌,因此這部分就可以被覆蓋重用;
3、若此時重做日誌還需要使用,那麼必須強制產生Checkpoint,將緩衝池中的頁至少重新整理到當前重做日誌的位置。

對於InnoDB儲存引擎,通過LSN(Log Sequence Number) 來標記版本。LSN是8位元組的數字,其單位是位元組,每個頁有LSN,重做日誌也有LSN,Checkpoint也有LSN。可以通過 show engine innodb status ; 檢視Checkpoint發生的時間、條件及髒頁的選擇等都非常複雜,無外乎是將緩衝池中的髒頁刷回到磁碟,不同之處在於每次重新整理多少頁到磁碟,每次從哪裡取髒頁,以及什麼時間觸發Checkpoint。

兩種CheckPoint:
1.Sharp CheckPoint
2.Fuzzy CheckPoint

這裡寫圖片描述

4.Master Thread工作方式

Innodb 1.2.x之前:主要包括主loop、background loop、flush loop和suspend loop。其中的引數可以配置。

while(true){

    //差不多1s一次
    for(int i in 0..9){
        重新整理日誌快取到磁碟
        //1s內的統計值
        if IO < 5
            合併插入快取
        if 髒頁比例 > 預定值
            重新整理部分髒頁(不超過100if  沒有使用者活動
            進入background loop{
                刪除無用undo頁
                合併20個插入緩衝
                可能跳到flush loop{
                    可能跳到suspend loop
                }
                跳回主loop
            }
        sleep 1s;
    }
    //差不多10s一次
    if IO < 200 //10s內
    重新整理100個髒頁到磁碟
    合併最多5個插入緩衝
    重新整理日誌緩衝
    刪除無用undo
    重新整理10010個髒頁
}

Innodb 1.2.x:Master Thread中的髒頁重新整理功能完全由Page Cleaner Thread執行。

if innodb is idle
    執行每10s一次的操作
else
    執行每秒執行的操作

5.Innodb關鍵特性

5.1插入緩衝

對於非聚集索引的插入或更新操作,不是每一次直接插入到索引頁中,而是先判斷插入的非聚集索引頁是否在緩衝池中,若在,則直接插入,若不在,則先放入到一個Insert Buffer物件中。資料庫這個非聚集的索引已經插到葉子結點,而實際沒有。只是存放在另一個位置。然後再以一定的頻率和情況進行Insert Buffer和輔助索引頁子節點的merge操作,這時通常能將多個插入合併到一個操作中(因為在一個索引頁中),這就大大提高了對於非聚集索引插入的效能。
使用Insert Buffer需要同時滿足條件:
1.索引是輔助索引
2.索引不是唯一的

change buffer
change buffer 適用的物件依然是非唯一的輔助索引。對一條記錄進行update操作可能分為兩個過程:
1.將記錄標記為已刪除(delete buffer)
2.真正將記錄刪除(purge buffer)

這裡寫圖片描述

Insert Buffer 內部實現
Insert Buffer 是一棵B+ 樹,由葉節點和非葉節點組成。非葉節點存放的是查詢的鍵值。
這裡寫圖片描述

這裡寫圖片描述

merge insert buffer
merge insert buffer 的操作可能發生在以下幾種情況下:
1.輔助索引頁被讀取到緩衝池中
2.insert buffer bitmap 頁追蹤到該輔助索引頁已無可用空間時
3.master thread
這裡寫圖片描述

5.2兩次寫

在對髒頁重新整理到磁碟時,如果某一頁還沒寫完就宕機,此時該頁資料已經混亂無法通過redo實現恢復。innodb提供了doublewrite機制。

這裡寫圖片描述

5.3自適應雜湊索引

這裡寫圖片描述
AHI有一個要求,即對這個頁的連續訪問模式必須是一樣的。

5.4 非同步IO

這裡寫圖片描述
AIO的另一個優勢是可以進行io merge 操作,也就是將多個io合併為1個io。

5.5重新整理鄰接頁

這裡寫圖片描述

6.啟動、關閉與恢復

InnoDB是mysql資料庫的儲存引擎之一,因此InnoDB儲存引擎的啟動和關閉,更準確的是指在mysql例項的啟動過程中對InnoDB儲存引擎的處理過程。
這裡寫圖片描述