從一條sql的執行流程來詳細瞭解Buffer Pool
阿新 • • 發佈:2021-12-19
從一條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,提高效能。