一、前言
終於《為研發同學同學定制的MySQL面試指南》第30篇更新來啦~
說來話長,都說Baidu是養老廠,結果偏偏干出了pdd的感覺。最近工作確實比較忙,然后周六日又想放松一下接連好多周六日都和同學出去游玩。
立個flag吧!后續的更新進度做到每周至少一更。歡迎關注白日夢,干貨分享不斷~
好!開始啦,做了這么久研發的你,有沒有聽別人說過、或者在哪里見過insert buffer
、change buffer
呢?這篇文章我們一起閑聊一下MySQL的insert buffer
、change buffer
,徹底揭開這兩個名詞的面紗!
二、問題引入
在白日夢看來,如果你想更好的理解 insert buffer、change buffer。首先你的先掌握一些前置的知識,比如MySQL索引的相關知識。所以不要着急,我們一點點展開話題,從你數據的知識過度到insert buffer、change buffer上去,你會發現豁然開朗。
2.1、聚簇索引
首先我們回顧一下MySQL的聚簇索引,這個東西大家肯定不陌生吧!我打賭在做的各位面試前都會背一背什么是聚簇索引。
大家可以看下面這張圖,它就是對B+tree的抽象。它有很多特性,在這篇文章中只需要知道如下幾個就好了
- 它是一個B+tree。
- 我們管這棵樹的葉子結點叫做數據頁。
- 葉子結點中存儲的數據行是一個全集,怎么解釋這個全集呢?比如數據表就3列,id、name、age。所謂的全集就是說:每行數據都有id、name、age這3列。
- 我們管非葉子結點叫做索引頁。
而且我跟大家講哦,這棵B+tree是會被存儲在Disk中的。如果你不能很好的理解的話,可以讀一下下面的兩端話:
比如一條update sql想修改id = 999的數據行,那它會怎么操作數據頁呢?簡單來說就是:首先會檢查一下buffer pool中有沒有包含這條數據的數據頁。如果有的話,直接update。如果沒有的話進行一次磁盤IO把該數據頁加載進內存,然后將其update。然后這時的數據頁也就變成了臟頁。等后續其它機制將該數據頁刷新回Disk。完成內存和數據。
讀上面的這段話,你要重點感受一下:數據頁從磁盤到Buffer Pool中的這個過程(最終會被掛載在B+tree的葉子結點上)
其實你類比着數據頁來看,對於B+tree的非葉子結點來說也是一樣的。上面我說了,我們管非葉子結點叫做索引頁。為啥這樣說呢?其實本質上非葉子結點也是數據頁,只不過它里面存儲的數據是索引數據。而且和普通的數據頁一樣,當你需要它而且它還不在內存中時,進行磁盤操作將其讀取內存中。
2.2、普通索引
普通的索引也就是我們常說的二級索引、聯合索引等等。
比如我們將name列設置成index的話,那么MySQL就會為我們這個索引單獨創建一個B+Tree。(是的!它是獨立於主鍵索引之外的另一顆B+Tree)。而且你注意一下如下幾點:
- 和聚簇索引一樣的是,我們管它的非葉子結點叫做:索引頁
- 它的葉子結點中存儲的並不是所有的列的全集。比如我們對name列創建索引,那么它的葉子節點中存儲的就是id、name兩列。並會按照name排序。
三、change buffer存在的意義
了解了上面的索引相關的前置知識點再來看insert buffer和change buffer那其實就很簡單了。
我們這一小節來看一下change buffer存在的意義:
其實說白了其實insert buffer也好,還是change buffer也好,它們其實就是MySQL在我們對非唯一的二級索引進行DML(刪除行、寫入行、修改行)操作時作出的優化邏輯。目的就是讓MySQL的性能更好。
比如還是我們這個例子:表里面有3列。id、name、age。然后id是主鍵、name是非唯一的二級索引。
一條update sql:update xx set name = "賜我白日夢" where name = “白日夢”
打過來之后,執行流程大概就像下面這樣:
1、檢查需要被update的數據是否在buffer pool中。
2、如果在buffer pool中直接將其update。
3、如果不在buffer pool中,進行磁盤的IO操作,將其讀取內存中,再把它update。
現在的問題是,name列是個索引列。上文也說了,既然是索引列就意味着需要為它單獨創建一顆B+Tree。
那你的update sql要做修改,那是不是會分成兩個大的步驟
1、Step1: 對buffer pool中的數據頁中的數據進行update。
2、Step2: 維護為name單獨創建的B+Tree。
你想呀既然MySQL要優化我們對非唯一的二級索引的DML操作,肯定要有個需要優化的點吧!
而這里的Step2,就是insert buffer和change buffer 存在的意思所在!
為啥這樣說呢?因為在本篇文章的開頭我們提到了,B+tree也是存儲在Disk中的,那它肯定就難免發生隨機磁盤IO。
或者你想一下:你只是想update 幾條數據。假設運氣很不好這幾條都沒有在buffer pool中。那沒辦法,我們只能去讀磁盤。但是更不巧的,涉及到的二級索引頁竟然也沒有在內存中,我們竟然還要同步等待這一次隨機磁盤IO!!!
四、再看change buffer
change buffer的本質上其實也是一塊內存。
比如你的:insert、delete、update等DML操作需要用到的二級索引頁(注意是二級索引頁,具體就比如說為name列這個二級索引創建的B+Tree的葉子節點,而不是Buffer pool中的普通數據頁)
就是當這些二級索引頁不在內存中時,你對它們的操作會被緩存在change buffer中(目的是省去這次隨機的磁盤IO)。等之后MySQL空閑了、或者是MySQL關閉前、或者是有讀取操作時再將這部分緩存操作merge到B+Tree中。
五、change buffer 的限制
這個現實其實已經說過了
1、首先得要求是二級索引。如果不是二級索引到話,那前面change buffer存在意義又是什么呢?沒有啥可優化的地方。那不如不要這個change buffer
2、要求二級索引不能唯一。這個很好理解。如果name列是唯一的。那我每次insert 之前是不是都必須去看下內存、Disk上到底有沒有已經存在的相同值的索引。這也就意味着這個insert 操作其實是不能被緩存的!必須立即知道到底能否insert 成功。對吧!不這樣的話,你打算返回給客戶端什么結果呢?
六、change buffer 相關參數
參數:innodb_change_buffer_max_size
作用:控制change buffer能占用buffer pool總內存的比例
范圍:默認25(表示change buffer最大能占用其25%的內存),最大50。
參數:innodb_change_buffering
作用:控制change buffer對哪些dml起作用
可選參數:all(insert、delete、update)、none(不緩存任何操縱)、inserts、deletes、purges
七、查看你的MySQL的change buffer
# 命令
SHOW ENGINE INNODB STATUS\G
# 查看如下部分
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
merged operations:
insert 0, delete mark 0, delete 0
discarded operations:
insert 0, delete mark 0, delete 0
Hash table size 4425293, used cells 32, node heap has 1 buffer(s)
13577.57 hash searches/s, 202.47 non-hash searches/s
# insert:insert buffer
# delete mask:delete buffer
# delete :purge buffer
# discarded operations:當change buffer發生merge時,數據表被刪除了!無需再merge
八、靈魂拷問
如果你能回答上這個問題,說明你真的理解了change buffer!
問:
我開啟change buffer 之后,現在要刪除一個非唯一的二級輔助索引數據行,比如就刪除name=Tom的行,並且這個索引頁不在內存中……接下來會發生什么?
按照change buffer的作用來說,是不是當索引頁不在內存中時,不去讀盤,而是會把這個刪除操作寫到change buffer 中?
那問題又來了,既然你是把這個操作寫到了change buffer中,那你返回給客戶端的影響行數怎么算出來的呢?你都沒有讀讀磁盤,萬一磁盤上都沒你要刪除的數據呢…… 你告訴客戶端,刪除成功了,影響行數為1?
答:其實客戶端每次都能得到正確的影響行數!不錯,change buffer中是把緩存了你的delete操作,但是buffer pool是沒有被影響的呀,如果buffer pool中沒有這個name=Tom的行,它依然會去讀磁盤的!你品一品,buffer pool和change buffer是兩塊緩存哦~
九、參考
https://dev.mysql.com/doc/refman/5.7/en/innodb-change-buffer.html
十、推薦閱讀
- MySQL的修仙之路,圖文談談如何學MySQL、如何進階!(已發布)
- 面前突擊!33道數據庫高頻面試題,你值得擁有!(已發布)
- 大家常說的基數是什么?(已發布)
- 講講什么是慢查!如何監控?如何排查?(已發布)
- 對NotNull字段插入Null值有啥現象?(已發布)
- 能談談 date、datetime、time、timestamp、year的區別嗎?(已發布)
- 了解數據庫的查詢緩存和BufferPool嗎?談談看!(已發布)
- 你知道數據庫緩沖池中的LRU-List嗎?(已發布)
- 談談數據庫緩沖池中的Free-List?(已發布)
- 談談數據庫緩沖池中的Flush-List?(已發布)
- 了解臟頁刷回磁盤的時機嗎?(已發布)
- 用十一張圖講清楚,當你CRUD時BufferPool中發生了什么!以及BufferPool的優化!(已發布)
- 聽說過表空間沒?什么是表空間?什么是數據表?(已發布)
- 談談MySQL的:數據區、數據段、數據頁、數據頁究竟長什么樣?了解數據頁分裂嗎?談談看!(已發布)
- 談談MySQL的行記錄是什么?長啥樣?(已發布)
- 了解MySQL的行溢出機制嗎?(已發布)
- 說說fsync這個系統調用吧! (已發布)
- 簡述undo log、truncate、以及undo log如何幫你回滾事物! (已發布)
- 我勸!這位年輕人不講MVCC,耗子尾汁! (已發布)
- MySQL的崩潰恢復到底是怎么回事? (已發布)
- MySQL的binlog有啥用?誰寫的?在哪里?怎么配置 (已發布)
- MySQL的bin log的寫入機制 (已發布)
- 刪庫后!除了跑路還能干什么?(已發布)
- 自導自演的面試現場,趣學數據庫的10種文件(已發布)
- 大型面試現場:一條update sql執行都經歷什么?(已發布)
- 大型翻車現場:如何實現記錄存在的話就更新,如果記錄不存在的話就插入。(已發布)