一線大廠,是不少人求之不得的盛典天堂。由於存在的無限的可能,能夠幫你實現本身的遠大抱負。大平臺機會、視野、格局每每都比小廠多不少。但隨之而來也是那高挑的技術門檻需等你邁過。好事物你們都喜歡,但畢竟僧多粥少。外加任務有難度,若是你沒過硬的本領,那很難踏入平臺,領會一覽衆山小的風采。不知你內心有沒有小九九?mysql
大廠產品大多數都成型好久,數據庫裏面存儲的數據都以海量計算,如何在這種規模下的數據中作到快速篩選呢?那就須要你來答。面試
你們思路確定和我同樣,話很少說,加索引再說!索引爲的就是提升數據的檢索效率,進而減小請求的響應時間。sql
這時,有內涵的人可能會反問你啦?數據庫
那你說說索引有哪些類型?索引底層實現是什麼結構?B+Tree的優勢?聚簇索引和非聚簇的區別?索引一次讀讀取多少數據合適?爲何說索引會下降插入、刪除、修改等維護任務的速度?數組
這一套組合拳,可能虐的你是體無完膚。讓人招教不住,心理一萬個xxx省略。送他一個微笑,而後再尷尬而不失優雅的離去。bash
你們可能都知道查詢慢了加索引,那爲啥加?在哪些字段上加?以及索引的數據結構特色。索引優化、優勢啥的都比較模糊或者不知道。服務器
今天將是對索引來一次靈魂的拷問,在進一步對索引優化、常見大廠面試問題、SQL優化等內容進行分享。網絡
這是個大工程,你們得關注再看。數據結構
深,那就得深出高度。MOG!太深啦函數
百度百科定義:索引是數據表中一列或多列的值進行排序的一種數據結構
故此,索引本質就是數據結構。這也是爲何每次數據表創建索引都須要設置在列字段上的緣由。那常見數據結構有哪些?
常見數據結構大體可分爲三大類,以下所示
線性表:順序表、鏈表、棧和隊列;
樹結構:二叉樹,堆、線索二叉樹、紅黑樹、B-Tree等;
圖存儲結構
但在數據庫中經常使用數據結構爲B+Tree、Hash索引。
對於此,有人可能以爲有了Hash和那麼多樹結構(紅黑樹、B樹、徹底平衡二叉(AVL)樹、B+樹),爲啥Mysql惟獨喜歡B+樹?
請聽以下分解:
首先上場的是頑固不變Hash索引,這Hash索引又是什麼?
哈希索引(hash index)基於哈希表實現,只有精確匹配索引全部列的查詢才能生效額。切記!切記!切記!
哈希的思路很簡單,以鍵-值( key-value )存儲數據的結構,對於待查找每一行的數據值,用一個哈希函數把數據值換算成一個肯定的位置即 key,位置就是哈希碼,而且不一樣鍵值的行計算出來的哈希碼也不同。而後在 value 上存放每一個數據行的指針。
對這樣的索引結構,執行以下sql語句的過程是什麼呢?
select * from nezha where name='lianhua'複製代碼
MySQL首先計算'lianhua'的哈希值,並使用該值尋找對應的記錄指針。而後根據指針尋找對應的數據,最後一步是比較讀取的值是否爲'lianhua', 從而來確保就是要查找的行。
那若是改變爲範圍性查找就會存在問題。還記得上面的切記嗎?由於它不支持範圍匹配,只支持等值匹配。例如:
select * from nezha where name like '%lianhua'複製代碼
那像Hash這種等值查詢還有哪些場景?
Hash故名思議體現的就是(key-value)結構。因此像 Redis、Memcached 及其餘一些 NoSQL 引擎(如 Memory)。
那有沒有既能快速查找,又能夠支持範圍型查找呢?
天然有,有序數組在等值查詢和範圍查詢場景中的性能就都很是OK,足以知足你的口味。
那它就好的沒天理啦?不,世上沒得十全十美的!
有序數組索引只適用於靜態存儲引擎,由於數組的空間必須是連續的,這就形成數組在內存中分配空間時必須找到一塊連續的內存空間。因此新增、刪除、修改數據時就會改變它的結構。
一下掉入無底洞,這在業務場景上怎樣使用?
靜態數據簡單點能夠理解爲不會在變化的數據,那你就能夠用於歷史歸檔性的業務。好比你去年酷狗歌單、每上月的支付記錄等,這類不會再修改的數據。
接下來上場的是層次不齊的樹結構
樹結構基礎就是普通二叉樹,其它樹結構都是基於它演進產生。二叉樹會根據元素值的大小來建立樹形結構。因此它是有序的,並支持範圍查找。具體可查看數據結構相關書籍。
但普通二叉樹,有個問題,就是當元素是遞增或遞減時,它就會退化爲線性表。
爲了解決這個問題,就出現了咱們的徹底平衡二叉樹。可爲什麼數據庫沒選擇它呢?
數據庫操做都是在內存裏面完成的,但最終仍是要落地到磁盤。若是數據多了,樹會變得很高。然而查詢數據時,那都是從磁盤裏面把數據讀取出來放入到內存中。這樣I/O操做成本就會隨着樹的高度而增長。這也是常說徹底平衡二叉樹具備高瘦特色。
好像女孩子都喜歡這樣的吧!
通常爲節約成本,不少公司服務器採用的仍是機械硬盤,這樣一次千萬級別的查詢差很少就要10秒,這還不算網絡傳輸、業務處理、CPU的執行時間,一但彙總那誰頂得住?
那這怎麼解決呢?不可能一直讓讓它變高吧! 可以使用B-Tree。
B-Tree的特徵就是矮胖,也稱爲多叉樹,就是在樹的同一高度上開闢多個分叉來容納元素。從而樹在橫向上面變寬了。這樣減小了磁盤I/O的查詢查找次數,從而提高了效率。
B-Tree的特色簡寫:
每一個節點中的元素(關鍵字)從小到大排列。
每一個節點都保存有數據
那爲何最終數據庫選擇了B+Tree,而不是B-Tree呢?
B+Tree天然保持了B-Tree的矮胖特徵,但它還作了升級的處理。就是讓葉子節點保存數據,而非葉子節點保存關鍵字便可,而且會有指針指向下一個葉子節點。這樣的好處是爲了提升範圍查找的效率。找到數據後直接根據指針向後讀取便可,而B-Tree就不行,當它讀取下一個數據,還須要再一次的進行索引樹的查找。
B+Tree特色:
全部的非葉子節點只存儲關鍵字信息
只有葉子節點存儲數據
全部葉子節點之間都有一個鏈指針
小結:最終MySQL選用B+Tree做爲索引,從而提升檢索索引時的磁盤IO效率,而且提升範圍查詢的效率,整個B+樹裏的元素也是有序的。由於B+Tree默認就是按照主鍵索引來構建的樹結構。那你說呢?
開發過程當中,MySQL都首先B+Tree。在MySQL下還擁有Hash索引,也就是它擁有2大索引類型。具體選擇用什麼,可在建立表時進行選擇。
那這索引究竟是怎麼建立出來的?
那還得分狀況而定,分爲如下2種
建表建索引
建立表的時候,先把索引字段創建好。如:
create table nezha(id int unsigned AUTO_INCREMENT PRIMARY KEY, phone int not null,name varchar(16),index (phone))engine=InnoDB;複製代碼
當添加數據時,數據庫就會自動先去建立好索引結構,而後建立數據。最終在落地到磁盤上。
先建表,後添索引
這種狀況須要注意,由於先建表,那可能你數據表已經擁有了大量數據,這時候你在添加索引,那你的整個數據庫確定會阻塞,由於數據庫須要根據表中數據創建索引,這都是由數據庫後臺線程來完成。
這也是爲何線上數據庫不要輕易變更索引,需根據用戶低峯時間來操做。因此索引建立過多,那也算是須要耗費資源的。
通常還須要維護表和索引,你這裏有什麼建議嗎?不妨留言說說你的提議,優化就留到下次。
因此當你的大表須要導入到其它數據庫時,需在新數據庫上先關閉索引,而後再添上索引,要否則效率就過低了。
乖乖,索引還有表現種類,這神馬狀況?
你們都知道B+Tree、Hash索引,但這些都底層實現的數據結構,而表現種類在明面上,咱們常說的,例如:聚簇索引、非聚簇索引等,都包含了對應的數據結構。
問最多算聚簇索引、非聚簇索引,那它們是什麼呢?
聚簇索引:索引和數據都存儲在一塊兒,表明做Innodb
非聚簇索引:索引和數據分開存儲,表明做MyIASM
上述的特性,也和它們的物理存儲文件有關係。文件放在數據庫安裝目錄下的data目錄中
/mysql-57/data/mysql複製代碼
MyISAM結構以下:
.frm爲表結構文件,存儲像create alter等語句 .MYD爲存儲數據文件 .MYI爲存儲索引文件
InnoDb結構以下:
.frm爲表結構文件,.ibd爲數據+索引文件
在InnoDB存儲引擎中,就必定都是聚簇索引嗎?
並非,只有主鍵索引被稱爲聚簇索引( clusteredindex )。除開主鍵之外的字段上建立的索引被稱爲非主鍵索引,非主鍵索引也被稱爲二級索引( secondary index )。
注:如今你該知道,爲啥面試都不問你什麼惟1、普通、聯合索引了吧,那都是屬於二級索引呢
那這二者之間有什麼區別嗎?區別在非主鍵索引的葉子節點內容是主鍵,當找到主鍵後,還須要根據主鍵再一次的進行索引樹的查找,這個過程稱之爲回表。
例如:
若是語句是 select * from nezha where ID=7 ,即主鍵查詢方式,那它只需搜索 ID 這棵 B+ 樹;
若是語句是 select * from nezha where name = '哪吒' ,即普通索引查詢方式,則須要先搜索 name 索引樹,獲得 ID的值爲 7,再到 ID 索引樹搜索一次。這就是所謂的回表。
那這個問題怎麼解決呀!
心裏獨白:哎呀!咋這麼多問題,煩不煩。
這個好辦,剛纔咱們是 select * ,查詢全部記錄,若是查詢字段上只出現主鍵索引與建立索引的字段,那就不須要回表了。由於走二級索引時,就已經包含了你須要的字段列啦,那就不須要在回表了。這就被稱之爲索引覆蓋,即索引已經包含了查詢操做的值。
這也是爲何,當有多個字段需建立索引時,會建立聯合索引,也是爲了更好支持索引覆蓋。
瞬間飛過,"我怎麼這麼好看,這麼好看怎麼辦"
搞了這麼久,那這個索引查找數據的時候,是怎麼個讀取原理又是什麼?
那這首先得說數據庫中的讀取數據單位,數據庫中的數據是按照頁讀取的。默認一頁的數據爲16KB。而磁盤塊(OS)默認爲4KB
show global variables like 'innodb_page%';複製代碼
那索引和數據都保存在節點裏面,這個數據怎麼個讀法?
上面說到,數據庫讀取數據是根據頁爲單位,而且讀的數據不知足1頁或超過1頁,那麼也會讀滿1頁。這也叫作預讀
也就是說節點讀取數據的大小應該控制在1頁、2頁、3頁、4頁等倍數頁大小最爲合適。
那你說說這個頁吧!
每一個數據頁中的數據,採用單向鏈表的形式進行鏈接。
各個頁之間採用雙向鏈表連接。
查找數據時是根據頁內分組定義的。首先在插入數據時就會根據主鍵大小作好排序結構,並按照最大和最小進行分組。
最小虛擬數據獨自一組,它擁有一條數據,就是最小數據。而後剩下的數據再分紅一組,即最大數據爲另外一組。當進行數據插入的時,都是先插入到最大數據組,當最大數據組裝滿後在進行分裂。
分組確立後,在進行數據查找的時就是根據二分查找法肯定對應數據所在的槽位置,而後在使用記錄頭信息的next_record一條條進行查找。
當以(非主鍵)做爲搜索條件:只能從最小虛擬數據記錄,開始依次遍歷單鏈表中的每條記錄。
因此,當寫複製代碼
select * from nezha where name='nezha'複製代碼
這樣沒有進行任何優化的sql語句,默認會這樣作:複製代碼
讀取記錄所在頁的範圍
根據雙向鏈表,找到所在頁
從所在頁中查找相應的記錄
因爲不是主鍵查詢,就遍歷所在頁的單鏈表
使用索引當中,最核心的就是最左匹配原則,索引命中都是根據它來定義的。
最左匹配原則:
索引能夠的簡單如單列 (a),也能夠複雜如多列 (a,b,c,d),即聯合索引。
若是是聯合索引,那麼key也由多個列組成,同時,索引只能用於查找key是否存在(相等),遇到範圍查詢 (>、<、BETWEEN、LIKE)等就不能進一步匹配了,後續退化爲線性查找。所以,列的排列順序決定了可命中索引的列數。
索引列不能是表達式的一部分,那樣沒法命中索引,例如
:SELECT * FROM nezha WHERE id + 1 = 5; date(create_time)='2020-03-05'複製代碼
例如:
若有索引 (a,b,c,d),查詢條件 a=7 and b=8 and c>15 and d=32,則會在每一個節點依次命中a、b、c,沒法命中d。(c已是範圍查詢了,d就沒辦法進行對比查找了)
總結:
索引在數據庫中是一個很是重要的內容!
最左前綴匹配原則。這是很是重要的原則,SQL查詢都是基於它來。MySQL會一直向右匹配,直到遇到範圍查詢 (>,<,BETWEEN,LIKE)就中止匹配。
頁也須要了解下,這個是數據庫在內部的工做機制。
索引的表現形式針對於不一樣的存儲引擎,表現也不同,而且2者之間的存儲引擎區別也要掌握瞭解
索引建立方式來自於建表前仍是建表後。重點都是數據庫再用後臺線程建立與維護索引
B+Tree和Hash這2個特色仍是須要注意,而且它們之間區別還未細講。後面會針對面試問題,給你們補上來。
若有幫助,歡迎關注@蓮花童子哪吒
及時收看更多好文
索引未完,下期在談,索引優化、細節、執行效率等等階段性內容