同窗, 還記得上一回說的回龍觀大叔面試的故事嘛? 通過上一回合的學習, 這位大叔終於找回了點自信, 此次又投了幾家公司, 不過如今尚未公司去聯繫他.mysql
大叔的電腦桌放在陽臺上, 這是個絕佳的位置, 天天清晨惺忪的陽光都能照進來, 但大叔可不想坐在那天天晚上數星星、白天曬太陽, 當小區裏面的年輕人還在熟睡的時候, 大叔早已經坐在書桌前開始繼續瞌 mysql 的知識了.面試
如下知識均爲大叔週末聊天時的口述, 我這裏整理下大概知識點.算法
大叔: 小兄弟, 大叔是面試的過來人, 這回合的知識很重要, 可要避免重走個人老路呀~sql
我把 Innodb 比喻成一本書, 那麼基本存儲單位就是頁, 一個頁的大小大約是16KB. 頁的類型也不同, 在書裏面有目錄頁、附錄頁、內容頁, Innodb 根據不一樣功能也有不少類型的頁, 好比存放INODE信息的頁、存放 undo 日誌信息的頁、存放數據索引的頁等等.markdown
這是一行數據的底層存儲結構, 看看我調的色都麼清新~ 性能
下面大叔解釋如下淡綠框字段的含義:學習
被刪除的記錄還在頁中麼? 他不會當即從頁中真正的移除掉, 行記錄中 delete_mask 就是標記已刪除的記錄, 全部被刪除掉的記錄都會組成一個所謂的垃圾鏈表,在這個鏈表中記錄佔用的空間稱之爲所謂的可重用空間,以後若是有新記錄插入到表中的話,可能把這些被刪除的記錄佔用的存儲空間覆蓋掉spa
若是面試官要問你, 爲何刪除掉記錄不真正移除掉?3d
你就答: 記錄與記錄之間是有關聯關係的, 移除它們以後把其餘的記錄在磁盤上從新排列須要性能消耗.日誌
min_rec_mask 用來標記 B+樹 的每層非葉子節點中的最小記錄
用來標記當前記錄的頁位置, 頁中行數據排列順序也是從小到大順序排列的, 值得一提的是每一個頁中自動加了兩條虛擬記錄, 一個記錄的是當前頁的主鍵最大記錄, 一個記錄的是最小記錄. 也就是上一回合提到的每一個 page頁 最少兩條記錄的緣由
當前行記錄類型
類型值 | 含義 |
---|---|
0 | 普通記錄(一般咱們插入的數據記錄) |
1 | B+樹非葉節點記錄(索引數據) |
2 | 頁最小記錄(虛擬記錄) |
3 | 頁最大記錄(虛擬記錄) |
距離下一條記錄的地址偏移量. 比方說第一條記錄的 next_record 值爲64,意味着從第一條記錄的真實數據的地址處向後找64個字節即是下一條記錄的真實數據, 若是 next_record 爲0, 則表示沒有下一條記錄了, 這個對於咱們數據檢索來講是很是重要的
n_owned 表示該組內共有幾條記錄
我問大叔具體這個是啥意思啊, 大叔說他沒搞明白, 若是面試官繼續問就說不知道就能夠了, 技術圈通常都已點到爲止, 不用深究.
查找一條記錄, 咱們須要遍歷全部頁嘛? 在數據量少的狀況, 咱們能夠根據頁中存的最大最小記錄查找, 可是數據量一多, 確定也是沒法忍受的. 那 innodb 是怎麼作的呢?
咱們仍是拿書舉例子, 書也是一頁一頁的, 咱們怎麼快速定位到某一章某一節的某一段內容呢? 聰明的小夥伴確定回答是: 目錄。
沒錯, 咱們看看 mysql 是怎麼實現頁 」頁級別目錄「 的
(此圖爲回龍觀大叔所盜《mysql是怎樣運行的》, 與本文做者無關)
簡單來講, 就是一個 page 頁中最大8條記錄分組, 將每組最小最大的值偏移量記錄到 slot 槽中, 這裏的 slot 就至關於目錄的一個做用, 下面是一個查數過程:
經過二分法肯定該記錄所在的槽,並找到該槽所在分組中主鍵值最小的那條記錄
經過記錄的 next_record 屬性遍歷該槽所在的組中的各個記錄
聽回龍觀大叔說這是讓他那天面試最心碎的地方, 須要我額外注意, 你們提起精神來啊~
大叔狂磕第一回咱們就知道在一個數據頁怎麼定位數據了, 可是咱們都是假設基於主鍵進行的查找哦。那麼若是不是主鍵呢?
這個就很悲劇了, 由於在數據頁中並無對非主鍵列創建所謂的頁目錄,因此咱們沒法經過二分法快速定位相應的槽。這種狀況下只能從最小記錄開始依次遍歷單鏈表中的每條記錄,而後對比每條記錄是否是符合搜索條件(下面會講到索引, 解決這個遍歷查詢慢問題)
一個數據頁大概只有 16KB, 咱們數據通常不可能只有這些, 確定須要多個數據頁存儲, 那麼多個頁之間是怎麼管理的呢?
頁和頁之間是雙向鏈表鏈接 (此圖爲回龍觀大叔所盜《mysql是怎樣運行的》, 與本文做者無關)
若是沒有索引的話, 默認是從頁a開始查知道頁b、頁c挨個查找, 直到知足指定的條件爲止.
當數據頁數據變大時, 將會由新增頁來存儲新數據, 這個過程就叫 頁分裂.
好比下圖是在頁10中插入記錄主鍵4, 而已存在記錄主鍵5, 當頁10已滿時, 新建立頁28, 進行的數據調整過程.
(此圖爲回龍觀大叔所盜《mysql是怎樣運行的》, 與本文做者無關)]
你們忽略頁號, 這個是沒有實際做用的, 真正的先後關係是使用頁之間雙向鏈表維護的.
由上面的規則能夠看出, 在對頁中的記錄進行增刪改操做的過程當中, 下一個數據頁中的主鍵值必須大於上一個頁中主鍵值, 因此咱們通常設置主鍵都會設置自增, 這樣是能夠避免頁滿時數據進行交換調整.
頁內查詢咱們冗餘 slot 空間來進行提升查詢速度, 可是對於這麼多頁, 總不能一個頁一個頁的掃描吧. 固然不行! 那咱們再抽象一層.
(此圖爲回龍觀大叔所盜《mysql是怎樣運行的》, 與本文做者無關)
咱們看這樣掃描頁是否是就很快了, 咱們基於上面數據就能夠很快定位到具體的頁了.
key 就是咱們說的索引
(此圖爲回龍觀大叔所盜《mysql是怎樣運行的》, 與本文做者無關)
如上圖所示, 同數據頁同樣, 咱們索引key也是也是頁管理的, 還記得上面 record_type 類型麼, record_type=1爲 B+ 樹非葉節點記錄, 普通數據爲 record_type=0
索引檢索、slot檢索都是經過二分查找算法實現的
若是數據量再大, 索引頁也會變得更多, 那麼咱們該怎麼辦呢? 再抽象一層!
可是這裏樹的高度並不能無限擴增哦, 由於每一層都表明了一次磁盤IO, 層級高磁盤IO次數變多將會致使查詢變慢. B+樹本質增大層的寬度來下降樹的高度, 因此通常咱們樹的高度也不會超過4層, 一個頁節點存放1000條數據, 一層1000個節點, 四層則是1000X1000X1000X10000=100000000000!!!
如上圖, Innodb 將全部數據存在葉子節點, 咱們一般稱這種存儲方式爲聚簇索引.
我理解聚簇索引對於實現行鎖是很是方便的, 主鍵查詢條目比較少時,不用回行. 可是若是碰到不規則數據插入時,形成頻繁的頁分裂
MyISAM的索引方案也是樹形結構,可是卻將索引和數據分開存儲的
當咱們基於二級索引查找數據時, 會給二級索引一樣創建一個相似的 B+ 樹, 不過葉子節點存儲的是主鍵的id, 再基於主鍵查詢葉子節點數據, 這種行爲咱們稱之爲回表.
而咱們查詢字段正好與索引徹底匹配, 不用再回表查詢數據, 稱爲覆蓋索引.
(此圖爲回龍觀大叔所盜, 與本文做者無關)
頁面和記錄先按照聯合索引前邊的列排序,若是該列值相同,再按照聯合索引後邊的列排序. 這種記錄模式決定了咱們直接使用後面的字段是沒法用到索引的, 由於單獨它自己就是無序的.
若是數據行數多和創建索引數據大, 索引是消耗存儲空間的.
在增刪改上都須要去修改各個B+樹索引, 尤爲對於無序的數據索引來講, 修改順序的後果是將會頻繁的進行插入修改頁、頁分裂, 對於 DML 性能來講是有損耗的.
只爲用於搜索、排序或分組的列建立索引
一般咱們只對索引數據分佈廣創建索引, 對於性別類是不適合創建索引的, B+樹內節點變成了二節點, 葉子節點數據彙集嚴重.
索引列的類型小, 對於大類型列佔據更多的存儲空間, 檢索效果很差, 能夠選定索引字符串值的前綴.
上一次見回龍觀大叔, 距離如今過去才幾天, 上次還抱怨找工做問他底層 mysql 知識, 此次感受很是樂於學習並向我分享這些底層基礎知識了. 可是一個跟我父親年齡相仿還天天在努力學習、找磚搬的大叔, 我不知道是應該替他擔憂仍是高興.