MySQL - InnoDB特性 - Buffer Pool漫談

緩存管理是DBMS的核心繫統,用於管理數據頁的訪問、刷髒和驅逐;雖然操做系統自己有page cache,但那不是專門爲數據庫設計的,因此大多數數據庫系統都是本身來管理緩存。因爲幾乎全部的數據頁訪問都涉及到Buffer Pool,所以buffer pool的併發訪問控制尤其重要,可能會影響到吞吐量和響應時間,本文主要回顧一下MySQL的buffer Pool最近幾個版本的發展(如有遺漏,歡迎評論補充), 感覺下最近幾年這一塊的進步php

MySQL5.5以前

只能設置一個buffer pool, 經過innodb_buffer_pool_size來控制, 刷髒由master線程承擔,擴展性差。mysql

MySQL 5.5

引入參數innodb_buffer_pool_instances,將buffer pool拆分紅多個instance,從而減小對buffer pool的訪問控制,這時候的刷髒仍是由Master線程來承擔。sql

MySQL 5.6

引入了buffer Pool page Id轉儲和導入特性,也就是說能夠隨時把內存中的page no存下來到文件裏,在重啓時會自動把這些Page加載到內存中,使內存保持warm狀態. 此外該版本第一次引入了page cleaner,將flush list/lru上的刷髒驅逐工做轉移到單獨線程,減小了master線程的負擔數據庫

MySQL 5.7

這個版本發佈了一個重要特性:online buffer pool resize. 固然是不是online須要打一個問號,由於在resize的過程當中須要拿不少全局大鎖,在高負載場景下很容易致使實例Hang住(81615)。 
和以前不一樣,buffer pool被分紅多個instance,每一個instance又由多個chunk組成,每一個chunk的大小受到參數innodb_buffer_pool_chunk_size控制,默認128MB, buffer pool resize都是以chunk爲單位增長或減小的。
另一個須要注意的點是:你配置的Buffer Pool Size可能比你實際使用的內存要大,尤爲對於大Bp而言,這是由於內部作了對齊處理, buffer pool size必須以 innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances來作向上對齊(80350)緩存

咱們知道一般數據文件的IO都被設置成O_DIRECT, 但每次修改後依然須要去作fsync,來持久化元數據信息,而對於某些文件系統而言是不必作fsync的,所以加入了新選項O_DIRECT_NO_FSYNC,這個需求來自於facebook. 他們也對此作了特殊處理:除非文件size變化,不然不作fsync。(最近在buglist上對這個參數是否安全的討論也頗有意思,官方文檔作了新的說明,感興趣的能夠看看 [94912:O_DIRECT_NO_FSYNC possible write hole
](https://bugs.mysql.com/bug.php?id=94912)))安全

再一個重要功能是終於引入了multiple page cleaner, 能夠多個後臺線程併發刷髒頁,提供了更好的刷髒性能,有效避免用戶線程進入single page flush。固然這還不夠完美,主要有四點:服務器

  1. 用戶線程依然會進入single page flush,而一旦大量線程進入,就會致使嚴重性能降低:超頻繁的fsync,激烈的dblwr競爭,線程切換等等
  2. 當redo空間不足時,用戶線程也會進入page flush,這在高負載場景下是很常見的,你會發現系統運行一段時間後,性能急劇降低。這是由於redo產生太快,而page flush又跟不上,致使checkpoint沒法推動。那麼用戶線程可能就要過來作fuzzy checkpoint了。那時候性能基本上無法看了。
  3. dblwr成爲重要的單點瓶頸。 若是你的服務器不支持原子寫的話,必須打開double write buffer。寫入Ibdata一段固定區域,這裏是有鎖包含的,區分爲兩部分:single page flush和batch flush, 但不管如何,即便拆分了多個page cleaner,最終擴展性仍是受限於dblwr
  4. 沒有專用的lru evict線程,都是Page cleaner鍵值的。舉個簡單的例子,當buffer pool佔滿,同時又有不少髒頁時,Page cleaner可能忙於刷髒,而用戶線程則得不到free page,從而陷入single page flush

若是你對上述幾個問題極不滿意,能夠嘗試percona server, 他們向來擅長優化Io bound場景的性能,而且上述幾個問題都解決了,尤爲是dblwr,他們作了多分區的改進。併發

MySQL 8.0

增長了一個功能,能夠在實例宕機時,core文件裏不去掉buffer pool, 這大大減小了core文件的大小。要知道,不少時候實例掛是由於文件損壞,不停的core重啓會很快把磁盤佔滿,你能夠經過設置參數innodb_buffer_pool_in_core_file來控制。數據庫設計

另外8.0最重要的一個改進就是:終於把全局大鎖buffer pool mutex拆分了,各個鏈表由其專用的mutex保護,大大提高了訪問擴展性。實際上這是由percona貢獻給上游的,而percona在5.5版本就實現了這個特性(WL#8423: InnoDB: Remove the buffer pool mutex 以及 bug#75534)。ide

原來的一個大mutex被拆分紅多個爲free_list, LRU_list, zip_free, 和zip_hash單獨使用mutex:

- LRU_list_mutex for the LRU_list;
  - zip_free mutex for the zip_free arrays;
  - zip_hash mutex for the zip_hash hash and in_zip_hash flag;
  - free_list_mutex for the free_list and withdraw list.
  - flush_state_mutex for init_flush, n_flush, no_flush arrays.

因爲log system採用lock-free的方式從新實現,flush_order_mutex也被移除了,帶來的後果是flush list上部分page可能不是有序的,進而致使checkpoint lsn和之前不一樣,再也不是某個log record的邊界,而是可能在某個日誌的中間,給崩潰恢復帶來了必定的複雜度(須要回溯日誌)

log_free_check也發生了變化,當超出同步點時,用戶線程再也不本身去作preflush,而是通知後臺線程去作,本身在那等待(log_request_checkpoint), log_checkpointer線程會去考慮log_consider_sync_flush,這時候若是你打開了參數innodb_flush_sync的話, 那麼flush操做將由page cleaner線程來完成,此時page cleaner會忽略io capacity的限制,進入激烈刷髒

8.0還增長了一個新的參數叫innodb_fsync_threshold,,例如建立文件時,會設置文件size,若是服務器有多個運行的實例,可能會對其餘正常運行的實例產生明顯的衝擊。爲了解決這個問題,從8.0.13開始,引入了這個閾值,代碼裏在函數os_file_set_size注入,這個函數一般在建立或truncate文件之類的操做時調用,表示每寫到這麼多個字節時,要fsync一次,避免對系統產生衝擊。這個補丁由facebook貢獻給上游。

其餘

固然也有些輔助結構來快速查詢buffer pool:

  • adaptive hash index: 直接把葉子節點上的記錄索引了,在知足某些條件時,能夠直接定位到葉子節點上,無需從根節點開始掃描,減小讀的page個數
  • page hash: 每一個buffer pool instance上都經過輔助的page hash來快速訪問其中存儲的page,讀加s鎖,寫入新page加x鎖。page hash採用分區的結構,默認爲16,有一個參數innodb_page_hash_locks,但很遺憾,目前代碼裏是debug only的,若是你想配置這個參數,須要稍微修改下代碼,把參數定義從debug宏下移出來
  • change buffer: 當二級索引頁不在時,能夠把操做緩存到ibdata裏的一個btree(ibuf)中,下次須要讀入這個page時,再作merge;另外後臺master線程會也會嘗試merge ibuf。

最後,據說官方正在努力解決double write buffer的瓶頸問題,期待一下.



本文做者:zhaiwx_yinfeng

閱讀原文

本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索