天天看點

走近InnoDB記憶體結構

文章目錄

    • 摘要
    • 整體結構
    • 記憶體結構
      • Buffer Pool
        • LRU
        • Buffer Pool List
        • Buffer Pool配置
        • flush
        • Change Buffer
      • Log Buffer
      • Adaptive Hash Index
    • 參考

摘要

本文基于MySQL5.7為基礎,讨論InnoDB記憶體結構的相關内容。其中涉及到Buffer Pool、Change Buffer、Adaptive Hash Index和Log Buffer等内容。适合有一定MySQL基礎的人閱讀。

整體結構

我們先來看看MySQL官方給出的InnoDB的整體結構,如下圖,從圖中可以看出,大緻可以分為左右兩個部分,左邊描述的是記憶體結構,就是本文讨論的主要内容,右邊描述的是磁盤結構,會在之後的文章裡講述。

走近InnoDB記憶體結構

記憶體結構

從上圖可以看出,内容結構又可以細分為三個部分,分别為Buffer Pool、Adaptive Hash Index和Log Buffer,接下來我們就來看看他們分别是什麼,又有什麼用呢。

Buffer Pool

Buffer Pool(緩沖池)是主記憶體中的一個區域,InnoDB在通路表和索引資料時将緩存在其中,來加速資料通路。大家都知道,MySQL讀取資料的最小機關是Page(頁),就算你一次查詢的結果隻有一行資料,但是MySQL會把這行資料所在的Page加載到Buffer Pool,是以Buffer Pool中的結構也是Page,每一個Page可能包含多行資料。

LRU

為了提高緩存的命中率,每一種緩存都會有适合自己的緩存淘汰算法,Buffer Pool也不例外。Buffer Pool使用的是LRU(最近最少使用)的變體,這種算法被廣泛應用,例如作業系統、Redis等等。這種算法能最大化頁面命中率。關于這種算法的更多資訊,可自行Google。

Buffer Pool List

當需要空間将新頁添加到緩沖池時,将收回最近使用最少的頁,并将新頁添加到清單的中間。這個中點将清單分為兩個子清單:

  • New Sublist:最近通路的新頁子清單。
  • Old Sublist:最近通路次數較少的舊頁子清單。
走近InnoDB記憶體結構

這裡說一下這個變體算法的過程:

  • 5/8配置設定給新子清單,3/8的容量配置設定給舊子清單。
  • 清單的“中點”是新子清單的尾部與舊子清單的頭部相交的邊界。
  • InnoDB

    将頁面讀入緩沖池時,它首先将其插入中點(舊子清單的頭部)。
  • 通路舊子清單中的頁面使其變為“ 年輕 ”,将其移至新子清單的頭部(頭插法)。
  • 當資料庫運作時,緩沖池中不被通路的頁面會“老化”到清單的尾部。新的和舊的子清單中的頁面随着其他頁面的更新而老化。舊子清單中的頁面也會随着頁面插入到中點而老化。最終,一個頁面到達舊子清單的尾部并被逐出。

你可能會覺得疑惑,為什麼要在清單的“中點”(也就是舊子清單的頭部)插入,再通過通路舊子清單使其插入清單的頭部(也就是新子清單的頭部),而不是直接插入清單的頭部呢?這不是多此一舉麼?

其實并不是多次一舉,你試想一下,新子清單中存放的是我們大量的熱點資料,這個時候有一個大表的全表查詢或者mysqldump(邏輯備份),如果是直接插入清單的頭部會有什麼結果,我們新子清單中的所有熱點資料全部被“老化”到舊子清單中,甚至直接被淘汰,其實我們并不希望如此。因為才會選擇通過再次通路舊子清單的頁使其變為“年輕”。

Buffer Pool配置

你可以通過Buffer Pool的相關配置提高性能:

  • Buffer Pool配置設定的記憶體越大,查詢性能越高;
  • 在64位的系統上,可以配置多個Buffer Pool執行個體,以最大程度減少并發操作之間記憶體結構的競争。
  • 您可以控制何時進行flush(刷髒頁),以及是否根據工作負荷動态調整刷髒頁速率。

flush

當記憶體資料頁跟磁盤資料頁内容不一緻的時候,我們稱這個記憶體頁為“髒頁”。

正常運作中的執行個體,資料寫入後的最終落盤,是從redo log更新過來的還是從buffer pool更新過來的呢?

實際上,redo log并沒有記錄資料頁的完整資料,是以它并沒有能力自己去更新磁盤資料頁。

  • 如果是正常運作的執行個體的話,資料頁被修改以後,跟磁盤的資料頁不一緻,稱為髒頁。最終資料落盤,就是把記憶體中的資料頁寫盤。這個過程,甚至與redo log毫無關系。
  • 在崩潰恢複場景中,InnoDB如果判斷到一個資料頁可能在崩潰恢複的時候丢失了更新,就會将它讀到記憶體,然後讓redo log更新記憶體内容。更新完成後,記憶體頁變成髒頁,就回到了第一種情況的狀态。

是以說,redo log隻是用來恢複記憶體中的資料,更新資料是從記憶體頁更新到磁盤的。

刷髒頁雖然是常态,但是出現以下這兩種情況,都是會明顯影響性能的:

  • 查詢導緻buffer pool要淘汰的髒頁個數太多,會導緻查詢的響應時間明顯變長;
  • 日志寫滿,更新全部堵住,寫性能跌為0,這種情況對敏感業務來說,是不能接受的。

是以,InnoDB需要有控制髒頁比例的機制,來盡量避免上面的這兩種情況。

Change Buffer

Change Buffer(更改緩存區),是一種特殊的資料結構,當輔助索引頁不在

Buffer Pool

中時,它會将

更改

緩存到輔助索引頁。由插入、更新或删除操作(DML)引起的緩沖

更改

将在稍後由其他讀取操作将頁加載到

Buffer Pool

中時合并。

走近InnoDB記憶體結構

與聚集索引不同,二級索引通常是非唯一的,插入二級索引的順序相對随機。類似地,删除和更新可能會影響索引樹中不相鄰的輔助索引頁。當受影響的頁被其他操作讀取到

Buffer Pool

中時,合并緩存的更改可以避免從磁盤将輔助索引頁讀取到緩沖池中所需的大量随機I/O通路。

是以,Change Buffer中存儲的是更改了的輔助索引頁。

如果索引包含降序索引列或主鍵包含降序索引列,則不支援對輔助索引進行更改緩沖。

Log Buffer

Log Buffer(日志緩沖區),用于儲存要寫入磁盤上的日志檔案的資料。日志緩沖區大小由innodb_Log_buffer_size變量定義。預設大小為16MB。日志緩沖區的内容定期重新整理到磁盤(寫入重做日志檔案)。大型日志緩沖區使大型事務能夠運作,而無需在事務送出之前将重做日志資料寫入磁盤。是以,如果您有更新、插入或删除許多行的事務,增加日志緩沖區的大小可以節省磁盤I/O。

innodb_flush_log_at_trx_commit

變量控制如何将日志緩沖區的内容寫入并重新整理到磁盤。該

innodb_flush_log_at_timeout

變量控制日志重新整理頻率。

Adaptive Hash Index

Adaptive Hash Index(自适應哈希索引),使

InnoDB

可以在不犧牲事務功能或可靠性的情況下,在工作負載和緩沖池有足夠記憶體的适當組合的系統上執行更像是記憶體資料庫。

使用索引關鍵字的字首建構哈希索引。字首可以是任意長度,并且可能隻有B樹中的某些值出現在哈希索引中。哈希索引是根據需要為經常通路的索引頁建構的。

就像上面整體結構圖中描述的一樣,自适應哈希索引可以加快對Buffer Pool中索引頁的通路。

參考

[1] MySQL 5.7 Reference Manual.

[2] MySQL實戰45講.

繼續閱讀