騰訊雲數據庫負責人林曉斌說過:「咱們面試 MySQL 同事時只考察兩點,索引和鎖」。程序員
言簡意賅,MySQL 索引的重要性不言而喻。MySQL 索引歷經了多個版本的迭代,從語法到底層數據結構都有不少改變。web
MySQL 索引,咱們真的瞭解麼?好了,今天咱們一塊兒來看看 MySQL 索引的前世此生,一塊兒聊聊索引的那些事兒。面試
什麼是索引?算法
在關係數據庫中,索引是一種單獨的、物理的對數據庫表中一列或多列的值進行排序的一種存儲結構,它是某個表中一列或若干列值的集合和相應的指向表中物理標識這些值的數據頁的邏輯指針清單。
sql
索引的做用至關於圖書的目錄,能夠根據目錄中的頁碼快速找到所需的內容。數據庫
當表中有大量記錄時,若要對錶進行查詢:編程
第一種搜索信息方式是全表搜索,是將全部記錄一一取出,和查詢條件進行一一對比,而後返回知足條件的記錄,這樣作會消耗大量數據庫系統時間,並形成大量磁盤 I/O 操做。
服務器
第二種就是在表中創建索引,而後在索引中找到符合查詢條件的索引值,最後經過保存在索引中的 ROWID(至關於頁碼)快速找到表中對應的記錄。數據結構
MySQL 5.5 之後 InnoDB 儲引擎使用的索引數據結構主要用:B+Tree;本篇文章帶你們以 B+Tree 前世此生爲主線來聊一聊。app
Mark:B+Tree 能夠對 <,<=,=,>,>=,BETWEEN,IN,以及不以通配符開始的 LIKE 使用索引。(MySQL 5.5 後)
這些事實或許會顛覆你的一些認知,好比在你讀過的其餘文章或書中。以上這些都屬於「範圍查詢」,都是不走索引的!
沒錯,早在 5.5 之前,優化器是不會選擇經過索引搜索的,優化器認爲這樣取出的行多與全表掃描的行,由於還要回表查一次嘛,可能會涉及 I/O 的行數更多,被優化器放棄。
通過算法(B+Tree)優化後,支持對部分範圍類型的掃描(得利與 B+Tree 數據結構的有序性)。
該作法同時也違反了最左前綴原則,致使範圍查詢後的條件沒法用到聯合索引,咱們在後面詳細說明。
索引的優缺點
索引大大減少了服務器須要掃描的數據量。
索引能夠幫助服務器避免排序和臨時表。
索引能夠將隨機 I/O 變成順序 I/O。
雖然索引大大提升了查詢速度,同時卻會下降更新表的速度,如對錶進行 INSERT、UPDATE 和 DELETE。由於更新表時,MySQL 不只要保存數據,還要保存索引文件。
創建索引會佔用磁盤空間的索引文件。通常狀況這個問題不算嚴重,但若是你在一個大表上建立了多種組合索引,且伴隨大量數據量插入,索引文件大小也會快速膨脹。
若是某個數據列包含許多重複的內容,爲它創建索引就沒有太大的實際效果。
對於很是小的表,大部分狀況下簡單的全表掃描更高效。
所以應該只爲最常常查詢和最常常排序的數據列創建索引。(MySQL 裏同一個數據表裏的索引總數限制爲 16 個)
數據庫存在的意義之一就是是解決數據存儲和快速查找的。那麼數據庫的數據存在哪?沒錯,是磁盤,磁盤的優勢是啥?便宜!缺點呢?相比內存訪問速度慢。
那麼你知道 MySQL 索引主要使用的數據結構麼?B+樹!你脫口而出。
那 B+樹是什麼樣的數據結構?MySQL 索引又是爲何選擇了 B+樹呢?
其實最終選用 B+樹是經歷了漫長的演化:
二叉排序樹 → 二叉平衡樹 → B-Tree(B樹) → B+Tree(B+樹)
有小夥伴問我「B 樹跟 B-樹有什麼區別」?這裏普及一下,MySQL 數據結構只有B-Tree(B 樹)和 B+Tree(B+樹),多隻是讀法不一樣罷了,「B-Tree」 通常統稱爲 B 樹,你叫他 B-樹也行!
還有小夥伴提到的紅黑樹,是編程語言中的存儲結構,不是 MySQL 的;如 Java 的 HashMap 就是用的鏈表加紅黑樹。
好了,今天就帶着你們一塊兒看一下演化成 B+樹的過程吧。
B+Tree 索引的前世此生
①二叉排序樹
理解 B+樹以前,簡單說一下二叉排序樹,對於一個節點,它的左子樹的孩子節點值都要小於它自己,它的右子樹的孩子節點值都要大於它自己。
若是全部節點都知足這個條件,那麼它就是二叉排序樹。(此處能夠串一下二分查找的知識點)
上圖是一顆二叉排序樹,你能夠嘗試利用它的特色,體驗查找 9 的過程:
9 比 10 小,去它的左子樹(節點 3)查找。
9 比 3 大,去節點 3 的右子樹(節點 4)查找。
9 比 4 大,去節點 4 的右子樹(節點 9)查找。
節點 9 與 9 相等,查找成功。
一共比較了 4 次,那你有沒有想過上述結構的優化方式?
②AVL 樹(自平衡二叉查找樹)
上圖是 AVL 樹,節點個數和值均和二叉排序樹一摸同樣。
再來看一下查找 9 的過程:
9 比 4 大,去它的右子樹查找。
9 比 10 小,去它的左子樹查找。
節點 9 與 9 相等,查找成功。
一共比較了 3 次,一樣的數據量比二叉排序樹少了一次,爲何呢?由於 AVL 樹高度要比二叉排序樹小,高度越高意味着比較的次數越多;不要小看優化的這一次,假如是 200w 條數據,比較次數會明顯地不一樣。
你能夠想象一下一棵 100 萬節點的平衡二叉樹,樹高 20。一次查詢可能須要訪問 20 個數據塊。在機械硬盤時代,從磁盤隨機讀一個數據塊須要 10 ms 左右的尋址時間。
也就是說,對於一個 100 萬行的表,若是使用二叉樹來存儲,單獨訪問一個行可能須要 20 個 10 ms 的時間,這個查詢可真夠慢的!
B 樹是一種多路自平衡搜索樹,它相似普通的二叉樹,可是 B 樹容許每一個節點有更多的子節點。
B 樹示意圖以下:
B 樹的特色以下:
全部鍵值分佈在整個樹中。
任何關鍵字出現且只出如今一個節點中。
搜索有可能在非葉子節點結束。
在關鍵字全集內作一次查找,性能逼近二分查找算法。
爲了提高效率,要儘可能減小磁盤 I/O 的次數。實際過程當中,磁盤並非每次嚴格按需讀取,而是每次都會預讀。
磁盤讀取完須要的數據後,會按順序再多讀一部分數據到內存中,這樣作的理論依據是計算機科學中註明的局部性原理:
因爲磁盤順序讀取的效率很高(不須要尋址時間,只需不多的旋轉時間),所以對於具備局部性的程序來講,預讀能夠提升 I/O 效率。預讀的長度通常爲頁(page)的整倍數。
MySQL(默認使用 InnoDB 引擎),將記錄按照頁的方式進行管理,每頁大小默認爲 16K(能夠修改)。
B-Tree 藉助計算機磁盤預讀機制:每次新建節點的時候,都是申請一個頁的空間,因此每查找一個節點只須要一次 I/O;由於實際應用當中,節點深度會不多,因此查找效率很高。
那麼最終版的 B+樹是如何作的呢?
從圖中也能夠看到,B+樹與 B 樹的不一樣在於:
全部關鍵字存儲在葉子節點,非葉子節點不存儲真正的 data,從而能夠快速定位到葉子結點。
爲全部葉子節點增長了一個鏈指針,意味着全部的值都是按順序存儲的,而且每個葉子頁到根的距離相同,很適合查找範圍數據。
所以,B+Tree 能夠對 <,<=,=,>,>=,BETWEEN,IN,以及不以通配符開始的 LIKE 使用索引。
B+ 樹的優勢,比較的次數均衡,減小了 I/O 次數,提升了查找速度,查找也更穩定:
B+樹的磁盤讀寫代價更低。
B+樹的查詢效率更加穩定。
要知道的是,你每次建立表,系統會爲你自動建立一個基於 ID 的彙集索引(上述 B+樹),存儲所有數據。
你每次增長索引,數據庫就會爲你建立一個附加索引(上述 B+樹),索引選取的字段個數就是每一個節點存儲數據索引的個數,注意該索引並不存儲所有數據。
爲何 MySQL 索引選擇了 B+樹而不是 B 樹?
緣由有以下兩點:
B+樹更適合外部存儲(通常指磁盤存儲),因爲內節點(非葉子節點)不存儲 data,因此一個節點能夠存儲更多的內節點,每一個節點能索引的範圍更大更精確。也就是說使用 B+樹單次磁盤 I/O 的信息量相比較 B 樹更大,I/O 效率更高。
MySQL 是關係型數據庫,常常會按照區間來訪問某個索引列,B+樹的葉子節點間按順序創建了鏈指針,增強了區間訪問性,因此 B+樹對索引列上的區間範圍查詢很友好。而 B 樹每一個節點的 key 和 data 在一塊兒,沒法進行區間查找。
程序員,你應該知道的索引知識點
好比你建立了 name, age 索引 name_age_index,查詢數據時使用了:
select * from table where name ='陳哈哈' and age = 26;
因爲附加索引中只有 name 和 age,所以命中索引後,數據庫還必須回去彙集索引中查找其餘數據,這就是回表,這也是你背的那條:少用 select * 的緣由。
結合回表會更好理解,好比上述 name_age_index 索引,有查詢:
select name, age from table where name ='陳哈哈' and age = 26;
B+樹的節點存儲索引順序是從左向右存儲,在匹配的時候天然也要知足從左向右匹配。
一般咱們在創建聯合索引的時候,也就是對多個字段創建索引,相信創建過索引的同窗們會發現,不管是 Oracle 仍是 MySQL 都會讓咱們選擇索引的順序。
好比咱們想在 a,b,c 三個字段上創建一個聯合索引,咱們能夠選擇本身想要的優先級,a、b、c,或者是 b、a、c 或者是 c、a、b 等順序。
爲何數據庫會讓咱們選擇字段的順序呢?不都是三個字段的聯合索引麼?這裏就引出了數據庫索引的最左前綴原理。
在咱們開發中常常會遇到明明這個字段建了聯合索引,可是 SQL 查詢該字段時卻不會使用索引的問題。
好比索引 abc_index:(a,b,c)是 a,b,c 三個字段的聯合索引,下列 sql 執行時都沒法命中索引 abc_index 的。
select * from table where c = '1';
select * from table where b ='1' and c ='2';
如下三種狀況卻會走索引:
select * from table where a = '1';
select * from table where a = '1' and b = '2';
select * from table where a = '1' and b = '2' and c='3';
從上面兩個例子你們是否闊以看出點眉目?
是的,索引 abc_index:(a,b,c),只會在(a)、(a,b)、(a,b,c)三種類型的查詢中使用。
其實這裏說的有一點歧義,其實(a,c)也會走,可是隻走 a 字段索引,不會走 c 字段。
另外還有一個特殊狀況說明下,下面這種類型的也只會有 a 與 b 走索引,c 不會走。
select * from table where a = '1' and b > '2' and c='3';
所以,在建立多列索引時,要根據業務需求,where 子句中使用最頻繁的一列放在最左邊。
仍是索引 name_age_index,有以下 sql:
select * from table where name like '陳%' and age > 26;
該語句有兩種執行可能:
命中 name_age_index 聯合索引,查詢全部知足 name 以"陳"開頭的數據, 而後回表查詢全部知足的行。
命中 name_age_index 聯合索引,查詢全部知足 name 以"陳"開頭的數據,而後順便篩出 age>20 的索引,再回表查詢全行數據。
顯然第 2 種方式回表查詢的行數較少,I/O 次數也會減小,這就是索引下推。因此不是全部 like 都不會命中索引。
使用索引時的注意事項
只要列中包含有 null 值都將不會被包含在索引中,複合索引中只要有一列含有 null 值,那麼這一列對於此複合索引就是無效的。因此咱們在數據庫設計時建議不要讓字段的默認值爲 null。
對串列進行索引,若是可能應該指定一個前綴長度。
例如,若是有一個 char(255)的列,若是在前 10 個或 20 個字符內,多數值是唯一的,那麼就不要對整個列進行索引。短索引不只能夠提升查詢速度並且能夠節省磁盤空間和 I/O 操做。
查詢只使用一個索引,所以若是 where 子句中已經使用了索引的話,那麼 order by 中的列是不會使用索引的。
所以數據庫默認排序能夠符合要求的狀況下不要使用排序操做;儘可能不要包含多個列的排序,若是須要最好給這些列建立複合索引。
通常狀況下不推薦使用 like 操做,若是非使用不可,如何使用也是一個問題。like 「%陳%」 不會使用索引而 like 「陳%」可使用索引。
這將致使索引失效而進行全表掃描,例如:
SELECT * FROM table_name WHERE YEAR(column_name)<2017;
這不屬於支持的範圍查詢條件,不會使用索引。
個人體會
曾經,我一度覺得我很懂 MySQL。
剛入職那年,我仍是個孩子,記得第一個需求是作個統計接口,查詢近兩小時每隔 5 分鐘爲一時間段的網站訪問量,JSONArray 中一共返回 24 個值,當時菜啊,寫了個接口循環二十四遍,發送 24 條 SQL 去查(捂臉)。
因爲那個接口,被技術經理嘲諷表示他寫的 SQL 比我吃的米都多。雖然咱們山東人基本不吃米飯,但我仍是羞愧不已。
而後經理經過調用一個 dateTime 函數分組查詢處理一下,就 OK 了,效率是個人幾十倍吧。
從那時起,我就定下目標,深刻 MySQL 學習,萬一往後有機會嘲諷回去?
筒子們,MySQL 路漫漫,其修遠兮。永遠不要眼高手低,一塊兒加油,但願本文能對你有所幫助。