MySQL內存管理機制


1. BufferPool

  • What is BufferPool?

MySQL InnoDB Buffer Pool,MySQL InnoDB 緩沖池。里面緩存着大量數據(數據頁),使 CPU 讀取或寫入數據時,不直接和低速的磁盤打交道,直接和緩沖區進行交互,從而解決了因為磁盤性能慢導致的數據庫性能差的問題。

  • Why need BufferPool?

buffer pool 最主要的功能便是加速讀加速寫

加速讀就是當需要訪問一個數據頁的時候,如果這個頁已經在緩存池中,那么就不再需要訪問磁盤,直接從緩沖池中就能獲取這個頁面的內容。

加速寫就是當需要修改一個數據頁的時候,先將這個頁在緩沖池中進行修改,記下相關的 redo log,這個頁的修改就算已經完成了。至於這個被修改的頁什么時候真正刷新到磁盤,這個是 buffer pool 后台刷新線程來完成的。

  • How implemented?

所有從磁盤加載進內存的數據頁,都會通過這個buffer pool管理起來。對應在代碼的中的結構體為buf_pool_t。在MySQL中通常會有多個buffer pool instance,這是為了減少多線程並發訪問時,buffer pool鎖等待的開銷

BufferPool由buf_pool, buf_chunk, buf_blockbuf_page組成,結構如下:

img

  • Code?
struct buf_pool_t {
  buf_chunk_t *chunks;         /*!< buffer pool chunks */
    
  hash_table_t *page_hash;     /*!< hash table of buf_page_t or
                               buf_block_t file pages,
                               buf_page_in_file() == TRUE,
                               indexed by (space_id, offset).
                               page_hash is protected by an
                               array of mutexes. */
    
  UT_LIST_BASE_NODE_T(buf_page_t) flush_list;
  /*!< base node of the modified block
  list */
  
  UT_LIST_BASE_NODE_T(buf_page_t) free;
  /*!< base node of the free
  block list */
    
  UT_LIST_BASE_NODE_T(buf_page_t) LRU;
  /*!< base node of the LRU list */
  
  /* ... */
}
struct buf_chunk_t {
  ulint size;           /*!< size of frames[] and blocks[] */
    
  unsigned char *mem;   /*!< pointer to the memory area which
                        was allocated for the frames */
    
  buf_block_t *blocks;  /*!< array of buffer control blocks */
  
  /* ... */
}
struct buf_block_t {
  buf_page_t page; /*!< page information; this must
                   be the first field, so that
                   buf_pool->page_hash can point
                   to buf_page_t or buf_block_t */
    
  byte *frame;     /*!< pointer to buffer frame which
                   is of size UNIV_PAGE_SIZE, and
                   aligned to an address divisible by
                   UNIV_PAGE_SIZE */
    
  /* ... */
}
class buf_page_t {
 public:
  /** @name General fields
  None of these bit-fields must be modified without holding
  buf_page_get_mutex() [buf_block_t::mutex or
  buf_pool->zip_mutex], since they can be stored in the same
  machine word.  */
  /* @{ */

  /** Page id. */
  page_id_t id;

  /** Page size. */
  page_size_t size;

  /** Count of how manyfold this block is currently bufferfixed. */
  uint32_t buf_fix_count;

  /** type of pending I/O operation. */
  buf_io_fix io_fix;

  /** Block state. @see buf_page_in_file */
  buf_page_state state;
    
  /* ... */
}

2. 頁面管理機制

InnoDB 基於 LRU 算法管理 buffer pool 中的數據頁。一般情況下 list 頭部存放的是熱數據,就是所謂的 young page(最近經常訪問的數據),list 尾部存放的就是 old page(最近不被訪問的數據)。

LRU List:緩存了所有讀入內存的數據頁。包含三類:

  1. 未修改的頁面,可以從該鏈表中摘除,然后移到 Free List 中;
  2. 已修改還未刷新到磁盤的頁面;
  3. 已修改且已經刷新到磁盤的頁面,可並為第一類。

Free List:空閑內存頁列表,需要裝載(緩存)磁盤上數據頁的時候,從此列表取內存塊。

Flush List:在內存中被修改但還沒有刷新到磁盤的數據頁(臟頁)鏈表,內存中的數據跟對應磁盤上的數據不一致,屬於該鏈表的頁同樣存在於 LRU List 中,但反之未必

img

  • How read a page?

當訪問的頁面在緩存池中命中,則直接從緩沖池中訪問該頁面。如果沒有命中,則需要將這個 page 從磁盤上加載到緩存池,因此需要在緩存池中的 Free List 中找一個空閑的內存頁來緩存這個從磁盤讀入的 page。

但存在空閑內存頁被使用完的情況,不保證一定有空閑的內存頁。假如 Free List 為空,則需要想辦法產生空閑的內存頁。 首先是在 LRU List 中找可以替換的內存頁,查找方向是從列表的尾部開始找,如果找到可以替換的 page,將其從 LRU List 中摘除,加入 Free List,然后再去 Free List 中找空閑的內存頁。第一次查找最多只掃描 100 個 page,循環進行到第二次時,查找深度就是整個 LRU List。這就是 LRU List 的頁面淘汰機制。

如果在 LRU List 中沒有找到可以替換的頁,則進行單頁刷新,將臟頁刷新到磁盤后,再將釋放的內存頁加入到 Free List,最后再去 Free List 取。為什么只做單頁刷新呢?因為它的目的是獲取空閑內存頁,進行臟頁刷新是不得已而為之,所以只會進行一個頁的刷新,目的是為了盡快的獲取空閑內存頁。

因為 Free List 是一個公共的鏈表,所有的用戶線程都可以使用,存在爭用的情況。因此,自己產生的空閑內存頁有可能會剛好被其它線程所使用,因此用戶線程可能會重復執行上面的查找流程,直到找到空閑的內存頁為止。

通過數據頁訪問機制,可以知道當無空閑頁時產生空閑頁就成為了一個必須要做的事情。

如果需要通過刷新臟頁來產生空閑頁或者需要掃描整個 LRU List 來產生空閑頁,查找空閑頁的時間就會延長,這是一個 bad case。

因此,innodb buffer pool 中存在大量可以替換的頁,或者 Free List 中一直存在着空閑內存頁,對快速獲取空閑內存頁就起到了決定性的作用。

而在 innodb buffer pool 的機制中,是采用何種方式來產生空閑內存頁以及可以替換的內存頁呢?這就是下面要講的內容——臟頁刷新策略。

  • How to flush a dirty page?

MySQL線程后台會有flush線程,定期地將flush list的臟頁flush到磁盤上,這樣可以減輕check point的開銷,和頁面替換時,那些被替換頁面的flush開銷,而使得讀取頁面時間增長。flush list的頁面根據修改的時間從新到老進行排序,也即是最新的修改,在flush list的頭部,最老的修改在flush list的尾部。當flush時,從尾部取page flush到磁盤上。這樣的邏輯是跟checkpoint保持一致,checkpoint的流程也是從老到新一步步持久化page,所以可以加快checkpoint。

  • When to flush dirty page?
    1. 后台線程定期刷;
    2. redo log 寫滿了(強制刷);
    3. 內存不足(強制刷)。

參考:

MySQL--InnoDB Buffer Pool詳解

MySQL內存管理機制

MySQL buffer pool中的三種鏈


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM