1. 程式人生 > 其它 >從一條sql的執行流程來詳細瞭解Buffer Pool

從一條sql的執行流程來詳細瞭解Buffer Pool

從一條sql的執行流程來詳細瞭解Buffer Pool,從而知道為什麼mysql的效能這麼好!

一條更新語句的執行流程

為什麼Mysql不能直接更新磁碟上的資料而且設定這麼一套複雜的機制來執行SQL?

  • 因為來一個請求就直接對磁碟檔案進行隨機讀寫,然後更新磁碟檔案裡的資料效能可能相當差。
  • 因為磁碟隨機讀寫的效能是非常差的,所以直接更新磁碟檔案是不能讓資料庫抗住很高併發的。
  • Mysql這套機制看起來複雜,但它可以保證每個更新請求都是更新記憶體BufferPool,然後順序寫日誌檔案,同時還能保證各種異常情況下的資料一致性。
  • 更新記憶體的效能是極高的,然後順序寫磁碟上的日誌檔案的效能也是非常高的,要遠高於隨機讀寫磁碟檔案。
    正是通過這套機制,才能讓我們的MySQL資料庫在較高配置的機器上每秒可以抗下幾幹甚至上萬的讀寫請求。

大幅提高效能的核心點

  • buffer pool

buffer pool是什麼?

  • 是一塊記憶體區域,當資料庫操作資料的時候,把硬碟上的資料載入到buffer pool,不直接和硬碟打交道,操作的是buffer pool裡面的資料
  • 資料庫的增刪改查都是在buffer pool上進行,和undo log/redo log/redo log buffer/binlog一起使用,後續會把資料刷到硬碟上
  • 預設大小 128M

mysql的buffer pool使用的演算法的空間大小配置:innodb_buffer_pool_size

  • innodb_buffer_pool_size引數是控制buffer pool緩衝池的大小,一般建議大一點!;
  • 檢視當前的buffer pool大小:show global variables like 'innodb_buffer_pool_size';
  • 修改當前的buffer pool大小(2G):SET GLOBAL innodb_buffer_pool_size= 2147483648;
  • 資料庫只要一啟動,就會按照你設定的Buffer Pool大小,稍微再加大一點,去找作業系統申請一塊記憶體區域,作為Buffer Pool的記憶體區域。
  • 當記憶體區域申請完畢之後,資料庫就會按照預設的快取頁的16KB的大小以及對應的800個位元組左右的描述資料的大小,在Buffer Pool中劃分出來一個一個的快取頁和一個一個的他們對應的描述資料

buffer pool的資料結構

寫資料不一致:髒快取頁(flush連結串列)

  • 被更新過的快取頁,資料和磁碟上的資料不一致,所以是髒快取頁
  • 髒快取頁的資料是要刷到磁碟上的

大量的非常用頁加入到buffer pool中:緩衝池汙染

  • 觸發條件:當一個表的資料量比加大,並且查詢的結果集比較大,之後需要拿出來進行匹配的時候。會將大量的頁加入到buffer pool中。
  • 解決條件:只有滿足“被訪問”並且“在老生代停留時間”大於T,才會被放入新生代頭部。簡單說就是修改innodb_old_blocks_pct、innodb_old_blocks_time配置。(下文詳細介紹了這倆個配置)

最常用快取演算法:LRU

  • 頁已經在緩衝池裡,那就只做“移至”LRU頭部的動作,而沒有頁被淘汰;
  • 頁不在緩衝池裡,除了做“放入”LRU頭部的動作,還要做“淘汰”LRU尾部頁的動作;
  • mysql在LRU演算法上做了優化

mysql的buffer pool使用的演算法

  • 將LRU分為兩個部分:新生代(new sublist)、老生代(old sublist)
  • 老生代預設佔比37%,可以通過 innodb_old_blocks_pct 進行配置
  • 新老生代收尾相連,即:新生代的尾(tail)連線著老生代的頭(head);
  • 新頁(例如被預讀的頁)加入緩衝池時,只加入到老生代頭部:如果資料真正被讀取(預讀成功),才會加入到新生代的頭部;如果資料沒有被讀取,則會比新生代裡的“熱資料頁”更早被淘汰出緩衝池;
  • 資料頁載入到快取頁後,在1s之後,訪問該快取頁,該快取頁會被移動到熱資料區頭部。資料頁剛載入到快取頁後,在1s之內,訪問該快取頁,該快取頁是不會被移動到熱資料區頭部的。
  • 熱資料區的前1/4的快取頁如果被訪問,是不會移動到熱資料區頭部的;後3/4的快取頁被訪問了,才會移動到熱資料區頭部

預讀

  • 磁碟讀寫,並不是按需讀取,而是按頁讀取,一次至少讀一頁資料(一般是16K),如果未來要讀取的資料就在頁中,就能夠省去後續的磁碟IO,提高效率。
  • 資料訪問,通常都遵循“集中讀寫”的原則,使用一些資料,大概率會使用附近的資料,這就是所謂的“區域性性原理”,它表明提前載入是有效的,確實能夠減少磁碟IO。
  • 預讀失效:由於預讀(Read-Ahead),提前把頁放入了緩衝池,但最終MySQL並沒有從頁中讀取資料,稱為預讀失效

mysql的buffer pool的淘汰策略

  • 當快取頁用完的時候,把冷資料區尾部的快取頁刷盤清空,快取頁對應的資訊描述塊從lru連結串列中移除,加入到free連結串列當中
  • 有一個後臺執行緒,他會執行一個定時任務,這個定時任務每隔一段時間就會把LRU連結串列的冷資料區域的尾部的一些快取頁,刷入磁盤裡去,清空這幾個快取頁,把他們加入回free連結串列去;如果該快取頁也在flush連結串列中(該快取頁更新過),也需要把該快取頁從flush連結串列中移除

mysql的buffer pool的重新整理機制

  • 當innodb中的髒頁比例超過innodb_max_dirty_pages_pct_lwm(預設值為0,對於innodb_max_dirty_pages_pct_lwm表示不啟動這個功能,也就是說innodb_buffer_pool中的髒頁比例會操持在75%左右)的值時,這個時候innodb就會開始重新整理髒頁到磁碟。
  • 當innodb中的髒頁比例超過innodb_max_dirty_pages_pct_lwm(預設是75)的值,而且還超過innodb_max_dirty_pages_pct時innodb就會進入勤快重新整理模式(agressively flush)這個模式下innodb會把髒頁更快的重新整理到磁碟。
  • sharp checkpoint:當innodb要重用它之前的redo檔案時,就會把innodb_buffer_pool中所有與這個檔案有關的頁面都要重新整理到磁碟;這樣做就有可能引起磁碟的IO風暴了,輕者影響效能,重者影響可用性。

mysql的buffer pool使用的演算法的核心配置:innodb_old_blocks_pct、innodb_old_blocks_time

  • innodb快取池有2個區域一個是sublist of old blocks存放不經常被訪問到的資料,另外一個是sublist of new blocks存放經常被訪問到的資料
  • innodb_old_blocks_pct引數是控制進入到sublist of old blocks區域的數量,初始化預設是37.
  • innodb_old_blocks_time引數是在訪問到sublist of old blocks裡面資料的時候控制資料不立即轉移到sublist of new blocks區域,而是在多少微秒之後才會真正進入到new區域,這也是防止new區域裡面的資料不會立即被踢出。
  • 如果在業務中做了大量的全表掃描,那麼你就可以將innodb_old_blocks_pct設定減小,增到innodb_old_blocks_time的時間,不讓這些無用的查詢資料進入old區域,儘量不讓快取在new區域的有用的資料被立即刷掉。(這也是治標的方法,大量全表掃描就要優化sql和表索引結構了)
  • 如果在業務中沒有做大量的全表掃描,那麼你就可以將innodb_old_blocks_pct增大,減小innodb_old_blocks_time的時間,讓有用的查詢快取資料儘量快取在innodb_buffer_pool_size中,減小磁碟io,提高效能。