【InnoDB】緩衝池

 


如下的資料總結自:官方文檔和《MySQL技術內幕-INNODB存儲引擎》一書。前端

對INNODB存儲引擎緩衝池的那一段描述來自博文:http://www.ywnds.com/?p=9886 說句實話這片博文寫的很清楚,經過問答形式加緊邏輯性!node

這篇文字會詳細的說明INNODB存儲引擎的體系結構及特性。mysql

  • INNODB存儲引擎的內存管理
  • Checkpoint技術
  • INNODB存儲引擎的關鍵特性
    •   插入緩衝
    •       DOUBLEWRITE
    •       AHI(自適應哈希索引)
    •       異步IO
    •       刷新臨近頁
 

INNODB的體系結構

首先經過一張圖來講明INNODB存儲引擎的體系結構。算法

INNODB存儲引擎有多個內存塊,這些內存塊組成了一個大的內存池。(innodb_buffer_size)sql

INNODB的後臺線程主要做用:刷新內存池中的數據,保證緩衝池中的內存緩存的是最近的數據。 其二:將已修改的數據文件刷新到磁盤文件,同時保證數據庫發生異常的狀況下INNODB能恢復到正常狀態。數據庫

在上一篇博文中說明了INNODB存儲引擎後臺線程的具體做用: CLICK HERE!緩存

上面的圖片只是簡單地說明了INNODB存儲引擎的做用,下面是一張詳細的圖,說明了INNODB在具體是怎麼樣作這些事的!(圖片來自官方網站的MySQL5.7的文檔)服務器

下面咱們會詳細的說明這張圖的具體是怎麼工做的!數據結構

 

緩衝池

INNODB存儲引擎是基於磁盤存儲的,並將其記錄按照頁的方式進行管理。在數據庫系統中因爲CPU速度與磁盤速度之間的鴻溝,基於磁盤的數據庫系統一般使用緩衝技術來提升數據的總體性能。

緩衝池簡單來講就是一塊內存區域.經過內存的速度來彌補磁盤速度較慢對數據庫性能的影響。在數據庫中進行讀取頁的操做,首先將從磁盤讀到的頁存放在緩衝池中(這個過程稱做FIX),下一次讀取相同的頁時,首先判斷該頁是否是在緩衝池中,若在,稱該頁在緩衝池中被命中,直接讀取該頁。不然,讀取磁盤上的頁。

對於數據庫中頁的修改操做,首先修改在緩衝池中頁,而後再以必定的頻率刷新到磁盤,並非每次頁發生改變就刷新回磁盤,而是經過一種叫作checkpoint的機制把頁刷新會磁盤。

所以數據的操做都是對緩衝池進行的操做,而不是磁盤。

緩衝池的大小設置:

緩衝池配置能夠經過INNODB_BUFFER_POOL_SZIE來設置,官方文檔建議,緩衝池的大小最多應設置爲物理內存的80%,正常使用能夠設置爲(50%~80%)之間。

緩衝池是一塊內存,INNODB存儲引擎是經過頁的方式對這塊內存進行管理的。緩衝池中存儲的頁有: 索引頁,數據頁,插入緩衝,自適應哈希索引(AHI),INNODB存儲的鎖信息,數據字典信息等。緩衝池中的索引頁和數據頁只是佔據了緩衝池的很大一部分而已。如圖(圖片地址

 

從INNODB1.0.x開始,容許有多個緩衝池實例。每一個頁根據哈希值平均分配到不一樣的緩衝池實例中。這樣作的好處是減小數據庫內部的資源競爭,增長數據庫併發處理能力。

innodb_buffer_pool_instances: 設置有多少個緩衝池。一般建議把緩衝池個數設置爲CPU的個數。
在使用show engine innodb status\G的時候會以---BUFFER POOL 5的形式分別標識每個bp,全部的bp會均分INNODB_BUFFER_POOL_SZIE的大小。

緩衝池的管理

緩衝池的結構描述(或者組織形式,不太準確):

咱們已經知道這個Buffer Pool實際上是一片連續的內存空間,那如今就面臨這個問題了:怎麼將磁盤上的頁緩存到內存中的Buffer Pool中呢?直接把須要緩存的頁向Buffer Pool裏一個一個往裏懟麼?不不不,爲了更好的管理這些被緩存的頁,InnoDB爲每個緩存頁都建立了一些所謂的控制信息,這些控制信息包括該頁所屬的表空間編號(space id)、頁號(page number)、頁在Buffer Pool中的地址,一些鎖信息以及LSN信息(鎖和LSN這裏能夠先忽略),固然還有一些別的控制信息。

每一個緩存頁對應的控制信息佔用的內存大小是相同的,咱們就把每一個頁對應的控制信息佔用的一塊內存稱爲一個控制塊吧,控制塊和緩存頁是一一對應的,它們都被存放到 Buffer Pool 中,其中控制塊被存放到 Buffer Pool 的前邊,緩存頁被存放到 Buffer Pool 後邊,因此整個Buffer Pool對應的內存空間看起來就是這樣的:

控制塊和緩存頁之間的那個碎片是個什麼呢?你想一想啊,每個控制塊都對應一個緩存頁,那在分配足夠多的控制塊和緩存頁後,可能剩餘的那點兒空間不夠一對控制塊和緩存頁的大小,天然就用不到嘍,這個用不到的那點兒內存空間就被稱爲碎片了。固然,若是你把Buffer Pool的大小設置的剛恰好的話,也可能不會產生碎片~

前面咱們知道了緩衝池的結構。接下來講InnoDB存儲引擎是怎麼對緩衝池進行管理的?

當咱們最初啓動MySQL服務器的時候,須要完成對Buffer Pool的初始化過程,就是分配Buffer Pool的內存空間,把它劃分紅若干對控制塊和緩存頁。可是此時並無真實的磁盤頁被緩存到Buffer Pool中(由於尚未用到),以後隨着程序的運行,會不斷的有磁盤上的頁被緩存到Buffer Pool中,那麼問題來了,從磁盤上讀取一個頁到Buffer Pool中的時候該放到哪一個緩存頁的位置呢?或者說怎麼區分Buffer Pool中哪些緩存頁是空閒的,哪些已經被使用了呢?咱們最好在某個地方記錄一下哪些頁是可用的,咱們能夠把全部空閒的頁包裝成一個節點組成一個鏈表,這個鏈表也能夠被稱做Free鏈表(或者說空閒鏈表)。由於剛剛完成初始化的Buffer Pool中全部的緩存頁都是空閒的,因此每個緩存頁都會被加入到Free鏈表中,假設該Buffer Pool中可容納的緩存頁數量爲n,那增長了Free鏈表的效果圖就是這樣的:

從圖中能夠看出,咱們爲了管理好這個Free鏈表,特地爲這個鏈表定義了一個控制信息,裏邊兒包含着鏈表的頭節點地址,尾節點地址,以及當前鏈表中節點的數量等信息。咱們在每一個Free鏈表的節點中都記錄了某個緩存頁控制塊的地址,而每一個緩存頁控制塊都記錄着對應的緩存頁地址,因此至關於每一個Free鏈表節點都對應一個空閒的緩存頁。

有了這個Free鏈表事兒就好辦了,每當須要從磁盤中加載一個頁到Buffer Pool中時,就從Free鏈表中取一個空閒的緩存頁,而且把該緩存頁對應的控制塊的信息填上,而後把該緩存頁對應的Free鏈表節點從鏈表中移除,表示該緩存頁已經被使用了,而且把改頁寫入LRU鏈表!

不要由於走的太遠而忘記爲何出發。
簡單回顧一下,爲何講free list?是爲了講怎麼管理buffer pool對吧。那free list就至關因而數據庫服務剛剛啓動沒有數據頁時,維護buffer pool的空閒緩存頁的數據結構。

下面再來簡單地回顧Buffer Pool的工做機制。Buffer Pool兩個最主要的功能:一個是加速讀,一個是加速寫。加速讀呢? 就是當須要訪問一個數據頁面的時候,若是這個頁面已經在緩存池中,那麼就再也不須要訪問磁盤,直接從緩衝池中就能獲取這個頁面的內容。加速寫呢?就是當須要修改一個頁面的時候,先將這個頁面在緩衝池中進行修改,記下相關的重作日誌,這個頁面的修改就算已經完成了。至於這個被修改的頁面何時真正刷新到磁盤,這個是後臺刷新線程來完成的。

在初始化的時候,bp中全部的頁都是空閒頁(也就是free list的頁),須要讀數據時,就會從free鏈表中申請頁,由於物理內存不可能無限增大,可是數據庫的數據倒是在不停增大的,因此free鏈表的頁是會用完的,這時候應該怎麼辦?這時候咱們能夠考慮把已經緩存的頁從bp中刪除一部分,那麼究竟採用什麼樣的方式來刪除,究竟該刪除哪些已經緩存的頁?

爲了回答這個問題,咱們還須要回到咱們設立Buffer Pool的初衷,咱們就是想減小和磁盤的I/O交互,最好每次在訪問某個頁的時候它都已經被緩存到Buffer Pool中了。假設咱們一共訪問了n次頁,那麼被訪問的頁已經在緩存中的次數除以n就是所謂的緩存命中率,咱們的指望就是讓緩存命中率越高越好

怎麼提升緩存命中率呢?InnoDB Buffer Pool採用經典的LRU算法來進行頁面淘汰,以提升緩存命中率。當Buffer Pool中再也不有空閒的緩存頁時,就須要淘汰掉部分最近不多使用的緩存頁。不過,咱們怎麼知道哪些緩存頁最近頻繁使用,哪些最近不多使用呢?呵呵,神奇的鏈表再一次派上了用場,咱們能夠再建立一個鏈表,因爲這個鏈表是爲了按照最近最少使用的原則去淘汰緩存頁的,因此這個鏈表能夠被稱爲LRU鏈表(Least Recently Used)。當咱們須要訪問某個頁時,能夠這樣處理LRU鏈表

  • 若是該頁不在Buffer Pool中,在把該頁從磁盤加載到Buffer Pool中的緩存頁時,就把該緩存頁包裝成節點塞到鏈表的頭部。
  • 若是該頁在Buffer Pool中,則直接把該頁對應的LRU鏈表節點移動到鏈表的頭部。

可是這樣作會有一些性能上的問題,好比你的一次全表掃描或一次邏輯備份就把熱數據給衝完了,就會致使致使緩衝池污染問題!Buffer Pool中的全部數據頁都被換了一次血,其餘查詢語句在執行時又得執行一次從磁盤加載到Buffer Pool的操做,而這種全表掃描的語句執行的頻率也不高,每次執行都要把Buffer Pool中的緩存頁換一次血,這嚴重的影響到其餘查詢對 Buffer Pool 的使用,嚴重的下降了緩存命中率 !

因此InnoDB存儲引擎對傳統的LRU算法作了一些優化,在InnoDB中加入了midpoint。新讀到的頁,雖然是最新訪問的頁,但並非直接插入到LRU列表的首部,而是插入LRU列表的midpoint位置。這個算法稱之爲midpoint insertion stategy。默認配置插入到列表長度的5/8處。midpoint由參數innodb_old_blocks_pct控制。

midpoint以前的列表稱之爲new列表,以後的列表稱之爲old列表。能夠簡單的將new列表中的頁理解爲最爲活躍的熱點數據。

同時InnoDB存儲引擎還引入了innodb_old_blocks_time來表示頁讀取到mid位置以後須要等待多久纔會被加入到LRU列表的熱端。能夠經過設置該參數保證熱點數據不輕易被刷出。

【free 鏈表是空的,數據庫剛初始化的時候產生的,當須要讀取數據時,會從free list中申請一個頁,把從放入磁盤讀取的數據放入這個申請的頁中,這個頁的集合叫LRU鏈表】

上面說到了讀數據,下面說明寫數據:

前面咱們講到頁面更新是在緩存池中先進行的,那它就和磁盤上的頁不一致了,這樣的緩存頁也被稱爲髒頁(英文名:dirty page)。因此須要考慮這些被修改的頁面何時刷新到磁盤?以什麼樣的順序刷新到磁盤?固然,最簡單的作法就是每發生一次修改就當即同步到磁盤上對應的頁上,可是頻繁的往磁盤中寫數據會嚴重的影響程序的性能(畢竟磁盤慢的像烏龜同樣)。因此每次修改緩存頁後,咱們並不着急當即把修改同步到磁盤上,而是在將來的某個時間點進行同步,由後臺刷新線程依次刷新到磁盤,實現修改落地到磁盤。

可是若是不當即同步到磁盤的話,那以後再同步的時候咱們怎麼知道Buffer Pool中哪些頁是髒頁,哪些頁歷來沒被修改過呢?總不能把全部的緩存頁都同步到磁盤上吧,假如Buffer Pool被設置的很大,比方說300G,那一次性同步這麼多數據豈不是要慢死!因此,咱們不得再也不建立一個存儲髒頁的鏈表,凡是在LRU鏈表中被修改過的頁都須要加入這個鏈表中,由於這個鏈表中的頁都是須要被刷新到磁盤上的,因此也叫FLUSH鏈表,有時候也會被簡寫爲FLU鏈表。鏈表的構造和Free鏈表差很少,這就不贅述了。這裏的髒頁修改指的此頁被加載進Buffer Pool後第一次被修改,只有第一次被修改時才須要加入FLUSH鏈表(代碼中是根據Page頭部的oldest_modification == 0來判斷是不是第一次修改),若是這個頁被再次修改就不會再放到FLUSH鏈表了,由於已經存在。須要注意的是,髒頁數據實際還在LRU鏈表中,而FLUSH鏈表中的髒頁記錄只是經過指針指向LRU鏈表中的髒頁。而且在FLUSH鏈表中的髒頁是根據oldest_lsn(這個值表示這個頁第一次被更改時的lsn號,對應值oldest_modification,每一個頁頭部記錄)進行排序刷新到磁盤的,值越小表示要最早被刷新,避免數據不一致。

【理解髒頁的概念?髒頁是bp中被修改的頁,髒頁寄存在與lru鏈表中,也存在與flush鏈表中,flush鏈表中存在的是一個指向lru鏈表中具體數據的指針。所以只有lru鏈表中的頁第一次別修改時,對應的指針纔會存入到flush中,若之後再修改這個頁,則是直接更新對應的數據。】

這三個重要列表(LRU list, free list,flush list)的關係能夠用下圖表示:

Free鏈表跟LRU鏈表的關係是相互流通的,頁在這兩個鏈表間來回置換。而FLUSH鏈表記錄了髒頁數據,也是經過指針指向了LRU鏈表,因此圖中FLUSH鏈表被LRU鏈表包裹。

 

緩存中頁的定位:

咱們前邊說過,當咱們須要訪問某個頁中的數據時,就會把該頁加載到Buffer Pool中,若是該頁已經在Buffer Pool中的話直接使用就能夠了。那麼問題也就來了,咱們怎麼知道該頁在不在Buffer Pool中呢?難不成須要依次遍歷Buffer Pool中各個緩存頁麼?一個Buffer Pool中的緩存頁這麼多都遍歷完豈不是要累死?

再回頭想一想,咱們實際上是根據表空間號 + 頁號來定位一個頁的,也就至關於表空間號 + 頁號是一個key,緩存頁就是對應的value,怎麼經過一個key來快速找着一個value呢?那確定是哈希表了。

因此咱們能夠用表空間號 + 頁號做爲key,緩存頁做爲value建立一個哈希表,在須要訪問某個頁的數據時,先從哈希表中根據表空間號 + 頁號看看有沒有對應的緩存頁,若是有,直接使用該緩存頁就好,若是沒有,那就從Free鏈表中選一個空閒的緩存頁,而後把磁盤中對應的頁加載到該緩存頁的位置。

 

上面基本說明了bp是怎麼工做的,接下來咱們看一個實例的bp信息。

mysql> show engine innodb ststus;
....
BUFFER POOL AND MEMORY ---------------------- Total large memory allocated 5502402560 #總的內存是多少,字節爲單位 Dictionary memory allocated 991733 #爲數據字典分配的總內存 Buffer pool size 327680 #總的bp有多少個頁,每一個頁默認大小爲16K(innodb_page_size的數值) Free buffers 8192 #當數據庫剛啓動時,bp中沒有數據,會含有許多16KB的塊,這些塊就是free buffer。當讀取數據時,就從free list中申請一個塊,而後把這個塊放入lru列表中.Free buffers表示當前free列表頁中的數量。 Database pages 490679 #表示的就是lru列表中的頁,也就是數據頁。(可能狀況是free buffer+database pages的數量之和等於bp,由於緩衝池中還可能會被分配自適應哈希索引,lock信息,insert buffer等頁,這部分頁不須要lru算法維護,所以不存在lru列表中) Old database pages 180966 # lru列表中old部分的頁數量 Modified db pages 0 # 髒頁的數量。flush列表 Percent of dirty pages(LRU & free pages): 0.000 # Max dirty pages percent: 75.000 # Pending reads 0 # 等待讀入緩衝池的緩衝池頁數。 Pending writes: LRU 0, flush list 0, single page 0 # Pages made young 452994, not young 1694417 0.00 youngs/s, 0.00 non-youngs/s (將頁從lru列表的old部分加入到new部分時,稱此時的操做爲page made young.而由於innodb_old_blocks_time的設置致使頁沒有從old部分移動到new部分的操做稱爲page not made young.--pages made young:顯示了lru列表中頁移動到前端的次數。young/s, non-young/s表示每秒這兩類操做的次數。) Pages read 1436912, created 4603153, written 3896513 0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s LRU len: 490679, unzip_LRU len: 49074 I/O sum[0]:cur[0], unzip sum[0]:cur[0]
....
#這個頁數據的總體介紹能夠查看官方文檔: https://dev.mysql.com/doc/refman/5.7/en/innodb-buffer-pool.html

咱們還可使用統計表查看以下:
mysql> use information_schema;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select * from INNODB_BUFFER_POOL_STATS \G
*************************** 1. row ***************************
                         POOL_ID: 0
                       POOL_SIZE: 8191
                    FREE_BUFFERS: 7005
                  DATABASE_PAGES: 1186
              OLD_DATABASE_PAGES: 448
         MODIFIED_DATABASE_PAGES: 0
              PENDING_DECOMPRESS: 0
                   PENDING_READS: 0
               PENDING_FLUSH_LRU: 0
              PENDING_FLUSH_LIST: 0
                PAGES_MADE_YOUNG: 0
            PAGES_NOT_MADE_YOUNG: 0
           PAGES_MADE_YOUNG_RATE: 0
       PAGES_MADE_NOT_YOUNG_RATE: 0
               NUMBER_PAGES_READ: 1126
            NUMBER_PAGES_CREATED: 60
            NUMBER_PAGES_WRITTEN: 70
                 PAGES_READ_RATE: 0
               PAGES_CREATE_RATE: 0
              PAGES_WRITTEN_RATE: 0
                NUMBER_PAGES_GET: 22583
                        HIT_RATE: 0
    YOUNG_MAKE_PER_THOUSAND_GETS: 0
NOT_YOUNG_MAKE_PER_THOUSAND_GETS: 0
         NUMBER_PAGES_READ_AHEAD: 384
       NUMBER_READ_AHEAD_EVICTED: 0
                 READ_AHEAD_RATE: 0
         READ_AHEAD_EVICTED_RATE: 0
                    LRU_IO_TOTAL: 0
                  LRU_IO_CURRENT: 0
                UNCOMPRESS_TOTAL: 0
              UNCOMPRESS_CURRENT: 0
1 row in set (0.00 sec)

#這個表的字段信息和上面命令輸出的信息均可以用來查看當前bp的統計信息InnoDB
 

 

 數據訪問機制

1. 當訪問的頁面在緩存池中命中,則直接從緩衝池中訪問該頁面。另外爲了不查詢數據頁時掃描LRU,還爲每一個buffer pool instance維護了一個page hash,經過space id和page id能夠直接找到對應的page。通常狀況下,當咱們須要讀入一個Page時,首先根據space id(space id對應的是表)和page id找到對應的buffer pool instance。而後查詢page hash,若是page hash中沒有,則表示須要從磁盤讀取。

2. 若是沒有命中,則須要將這個頁面從磁盤上加載到緩存池中,所以須要在緩存池中的空閒列表中找一個空閒的內存塊來緩存這個從磁盤讀入的頁面。

3. 但存在空閒內存塊被使用完的狀況,不保證必定有空閒的內存塊。假如空閒列表爲空,沒有空閒的內存塊,則須要想辦法去產生空閒的內存塊。

4. 首先去LRU列表中找能夠替換的內存頁面,查找方向是從列表的尾部開始找,若是找到能夠替換的頁面,將其從LRU列表中摘除,加入空閒列表,而後再去空閒列表中找空閒的內存塊。第一次查找最多隻掃描100個頁面,循環進行到第二次時,會查找深度就是整個LRU列表。這就是LRU列表中的頁面淘汰機制。

5. 若是在LRU列表中沒有找到能夠替換的頁,則進行單頁刷新,將髒頁刷新到磁盤以後,而後將釋放的內存塊加入到空閒列表。而後再去空閒列表中取。爲何只作單頁刷新呢?由於這個函數的目的是獲取空閒內存頁,進行髒頁刷新是不得已而爲之,因此只會進行一個頁面的刷新,目的是爲了儘快的獲取空閒內存塊。

由於空閒列表是一個公共的列表,全部的用戶線程均可以使用,存在爭用的狀況。所以,本身產生的空閒內存塊有可能會恰好被其餘線程所使用,因此用戶線程可能會重複執行上面的查找流程,直到找到空閒的內存塊爲止。

經過數據頁訪問機制,能夠知道其中當無空閒頁時產生空閒頁就成爲一個必需要作的事情了。若是須要刷新髒頁來產生空閒頁面或者須要掃描整個LRU列表來產生空閒頁面的時候,查找空閒內存塊的時間就會延長,這個是一個bad case,是咱們但願儘可能避免的。所以,innodb buffer pool中存在大量能夠替換的頁面,或者free列表中一直存在着空閒內存塊,對快速獲取到空閒內存塊起決定性的做用。在innodb buffer pool的機制中,是採用何種方式來產生的空閒內存塊,以及能夠替換的內存頁的呢?這就是咱們下面要講的內容——經過後臺刷新機制來產生空閒的內存塊以及能夠替換的頁面。

 

 checkpoint技術

 當前事務數據庫系統廣泛採用了write ahead log策略,即當事務提交時,先寫重作日誌,再修改頁。當因爲數據庫宕機而致使數據丟失時,經過重作日誌來完成數據的恢復。

想一種狀況,當重作日誌變得很大時,數據庫的恢復時間就會變得很長,恢復代價變得很大?

checkpoint技術主要目的是:

  • 縮短數據庫恢復時間
  • 緩衝池不夠用時,將髒頁刷新到磁盤。
  • 重作日誌不可用時,刷新髒頁。

 當數據庫發生宕機時,數據庫不須要全部的重作日誌,由於checkpoint以前的頁都已經刷新回磁盤,故數據庫只須要對checkpoint以後的重作日誌進行恢復便可。

此外當緩衝池不夠用時,根據lru算法會釋放最近最少使用的頁,若此頁爲髒頁,那麼就須要強制執行checkpoint,將髒頁刷新回磁盤。

 由於當前事務數據庫系統對重作日誌的設計都是循環使用的,在寫入重作日誌時,若這部分重作日誌不可用【是由於數據庫在宕機恢復時若須要這使用這部分日誌,若此時想要使用這部分重作日誌(前面的不可用狀態的重作日誌)】則必須強制checkpoint,將緩衝池中的頁刷新到對應當前重作日誌的位置。

若重作日誌能夠被重用的部分是指這些重作日誌已經再也不須要(緩衝池中的頁和重作日誌的位置吻合),那就能夠直接覆蓋。

 在這裏刷新緩衝池中頁的時候,咱們提到過,要把頁刷新道道重作日誌的位置,那麼INNODB是怎麼肯定這些位置的?

INNODB使用LSN(log sequenct number)來標識頁刷新的位置。【在前面的字節數上加上寫入的字節數】

 每一個頁都有對應LSN數值,重作日誌有LSN,checkpoint也有LSN。

mysql> show engine innodb status\G
......
LOG
---
Log sequence number 293633237 #當前緩衝池中的lsn的值,也就是redo log的lsn
Log flushed up to   293633237 #當前磁盤中的lsn的值,是刷redo log file flush to disk中的lsn;
Pages flushed up to 293633237 #是已經刷到磁盤數據頁上的LSN;
Last checkpoint at  293633228 #上一次刷新的LSN
.....
 

在INNODB存儲引擎中,checkpint發生的時間,條件及髒頁選擇都很複雜,而checkpoint所作的事情就是把緩衝池中的髒頁刷新到磁盤。不一樣之處在於每次刷新多少髒頁以及何時出發checkpoint?【這是隻是簡單說明下】

在INNODB存儲引擎內部有兩種checkpoint方式:

  • Sharp Checkpoint
  • Fuzzy Checkpoint

Sharp Checkpoint發生在將數據庫關機時將全部的髒頁刷新回磁盤,這是默認的工做方式,即參數innodb_fast_shutdown=1.

可是若在數據庫運行時也使用Sharp Checkpoint,那麼數據庫的性能就會受到影響。故在INNODB內部使用Fuzzy Checkpoint的刷新方式,即每次只刷新一部分髒頁,而不是刷新全部的髒頁。

在INNODB內部在發生以下狀況時,會進行fuzzy checkpoint刷新。

Master Thread Checkpoint: 【異步刷新,每秒或每10秒從緩衝池髒頁列表刷新必定比例的頁回磁盤。異步刷新,即此時InnoDB存儲引擎能夠進行其餘操做,用戶查詢線程不會受阻】
FLUSH_LRU_LIST Checkpoint:InnoDB存儲引擎須要保證LRU列表中差很少有100個空閒頁可供使用。在InnoDB 1.1.x版本以前,用戶查詢線程會檢查LRU列表是否有足夠的空間操做。若是沒有,根據LRU算法,溢出LRU列表尾端的頁,若是這些頁有髒頁,須要進行checkpoint。所以叫:flush_lru_list checkpoint。
InnoDB 1.2.x開始,這個檢查放在了單獨的進程(Page Cleaner)中進行,而且可使用innodb_lru_scan_depth 參數控制LRU列表中可用頁的數量,默認是1024!好處:1.減小master Thread的壓力 2.減輕用戶線程阻塞。 異步/同步 Checkpoint:重作日誌不可用時,須要強制將一些頁刷新回磁盤,而此時髒頁是從髒頁列表中選擇的。 髒頁太多時強制checkpoint:髒頁數量太多時,強制進行checkpoint,當緩衝池中髒頁的數量佔據超過innodb_max_dirty_pages_pct設定的值時,就進行強制刷新。默認數值是75%。
 
 

INNODB的關鍵特性

插入緩衝

INNODB在插入非彙集的非惟一性索引時,會隨機插入的數據,這就會致使性能降低。所以INNODB採用了insert buffer來完成非彙集非惟一性索引的插入。當插入這些索引時,不是每一次直接插入到索引頁中,而是先判斷插入的非彙集索引是否在緩衝池中,若在,則直接插入;若不在,則先放入到一個insert buffer對象中,好似欺騙。而後再以必定的頻率進行insert buffer和輔助所引頁子節點的合併操做,這時一般能將多個插入合併到一個操做中,這就大大提升了對於非彙集索引的插入性能。

insert buffer須要同時知足如下兩個條件:

  • 索引時輔助索引
  • 索引不是惟一索引

輔助索引不能是惟一的,由於在插入緩衝時,數據庫並不去查找所引頁來判斷記錄的惟一性。若是去查找確定致使又忽悠離散型讀狀況發生,從而致使insert buffer失去了意義。

INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
#size:表示已經合併記錄頁的數量,free list:表示空閒列表的長度,seg size顯示當前insert buffer的大小, mergers:表示合併頁的數量 merged operations: insert 0, delete mark 0, delete 0 discarded operations: #表示change buffer發生merge,表已經被刪除,此時就無需再將記錄合併到輔助索引中。 insert 0, delete mark 0, delete 0 Hash table size 34673, node heap has 0 buffer(s) Hash table size 34673, node heap has 0 buffer(s) Hash table size 34673, node heap has 0 buffer(s) Hash table size 34673, node heap has 0 buffer(s) Hash table size 34673, node heap has 0 buffer(s) Hash table size 34673, node heap has 0 buffer(s) Hash table size 34673, node heap has 0 buffer(s) Hash table size 34673, node heap has 0 buffer(s) 0.00 hash searches/s, 0.00 non-hash searches/s

 

change buffer

INNODB從1.0.x開始引入了Change Buffer,從這個版本開始INNODB存儲引擎能夠對DNL操做---insert(insert buffer), delete(dlete buffer),update(purge buffer)都進行緩衝。

和以前同樣change buffer的對象依然是輔助的非惟一索引。

對一條記錄進行update操做包含兩個過程:1將記錄標記爲刪除,2:將記錄刪除。delete buffer對應update的第一個階段,purge buffer對應update的第二個階段。

同時INNODB存儲引擎還提供了參數innodb_change_buffering ,用來開啓各類buffer的選項。可選擇的值以下:

inserts, deletes, purges, changes, all, none.
#changes:表示inserts和deletes。
#all:表示啓用所用
#none:表示所有關閉。默認是all

在寫密集的狀況下,change buffer會佔用過多的緩衝池資源,在INNODB1.2版本中可使用innodb_change_buffer_max_size參數進行控制,默認數值是25,表示1/4.

inert buffer的內部實現:

【站位】

兩次寫

在數據庫發生宕機時,可能INNODB存儲引擎正在寫入某個頁到表中,而這個頁只寫了一部分,好比16KB的頁,只寫了前4KB,以後就發生了宕機,這種狀況被稱爲部分寫失效。

doublewrite由兩部分組成,一部分是內存中的double buffer,大小爲2M,另外一部分是物理磁盤上共享表空間的連續的128個頁,即2個區,大小爲2M。在對緩衝池進行髒頁刷新時,並不直接寫磁盤,而是會經過memcpy函數將髒頁首先複製到內存中的doublewrite buffer。以後經過doublewrite buffer再分兩次,每次1M順利地寫入共享表空間的物理磁盤上,而後立刻調用fsync函數,同步磁盤,避免寫緩衝帶來的問題。在這個過程當中doublewriter是連續的,所以開銷不大。

 

轉自:http://www.javashuo.com/article/p-gkcxducm-k.html

相關文章
相關標籤/搜索