本文總結了一些MySQL索引的基本概念和原理,若是能夠快速清晰回答這些問題能夠出門左轉提提寶貴建議。mysql
什麼是索引?索引爲何查詢快,索引的數據結構是什麼? 聚簇索引/非聚簇索引區別? 什麼是覆蓋索引? 惟一索引/普通索引? 單列索引/聯合索引區別? Full-index全文索引? 什麼是下推索引? 什麼是最左匹配,查詢回表? 哪些字段適合建索引? 爲何通常主鍵索引最好是自增加的, 儘可能短的數值類型? 爲何有些SQL不走索引? 索引的最佳實踐?web
索引的本質是空間換時間。算法
因此咱們經過索引這個緩存
來提升數據查詢的效率。sql
假如咱們本身設計數據庫索引的話,咱們會選取什麼樣的數據結構呢?下面咱們來分析下各類查詢常見的數據結構的性格,看看選誰是最合適的人選。數據庫
有序數組:等值查詢和範圍查詢場景中的性能就都很是優秀。特定值查詢
用二分法就能夠快速獲得,這個時間複雜度是 O(log(N))。相似between[x, y]的 範圍查詢
也比較快,先用值查詢二分法找到x, 而後向後遍歷,知道找到y。 可是他最大的問題是插入或者刪除一個新數據,這個新數據後面的整個數組都須要挪動,複雜度是O(N)。數組
HashMap:雖然能夠快速定位,值查詢的時間複雜度是O(1), 可是Hashmap沒有順序,進行範圍查詢的話複雜度高是O(N)。緩存
二叉樹查找樹BST:二叉樹的高度不均勻,不能自平衡,查找效率跟數據量有關(樹的高度),在極端狀況下(插入數據自己就是有序的)這課樹就退化成鏈表了,查詢實際複雜度是O(N)數據結構
紅黑樹:是平衡的BST,性能穩定在O(logN), 但由於是二叉樹,樹的高度隨着數據量增長而增長,而且須要再平衡。適合數據都在內存的狀況,好比Java裏的HashMap。可是在硬盤尋址的場景下IO成本會比較高。數據庫設計
B-Tree:相比二叉樹來講是一種多路平衡查詢樹,可是B樹無論葉子節點仍是非葉子節點,都會保存數據,這樣致使在非葉子節點中能保存的指針數量變少(有些資料也稱爲扇出),指針少的狀況下要保存大量數據,只能增長樹的高度,致使IO操做變多,查詢性能變低;編輯器
B+Tree: 從物理存儲結構上說是N叉樹,B-Tree和B+Tree都以頁(4K)來劃分節點的大小,可是因爲B+Tree的中間節點(非葉子節點)不存儲數據,存的是索引信息,索引包含Key和Point指針。 所以B+Tree可以在一樣大小的節點中,存儲更多的key,提升查找效率。
每個索引在 InnoDB 裏面對應一棵 B+ 樹。以 InnoDB 的一個整數字段索引爲例,這個 N 差很少是 1200。這棵樹高是 4 的時候,就能夠存 1200 ^(4-1) 個值,這已經 17 億了。考慮到樹根的數據塊老是在內存中的,一個 10 億行的表上一個整數字段的索引,查找一個值最多隻須要訪問 3 次磁盤。
區別主要看葉子節點存了什麼數據:
在 InnoDB 裏,索引B+ Tree的葉子節點存儲了整行數據的是主鍵索引,也被稱之爲聚簇索引。
而索引B+ Tree的葉子節點存儲了主鍵的值的是非主鍵索引,也被稱之爲非聚簇索引。
聚簇索引查詢相對會更快一些,由於主鍵索引樹的葉子節點直接就是咱們要查詢的整行數據了。而非主鍵索引的葉子節點是主鍵的值,查到主鍵的值之後,還須要再經過主鍵的值再進行一次查詢(這個過程叫作回表, 也就是查了2個索引樹)。
覆蓋索引(covering index)指一個查詢語句的執行只用從索引中就可以取得,沒必要從數據表中讀取。覆蓋索引不是索引樹,是一個結果。當一條查詢語句符合覆蓋索引條件時,MySQL只須要經過索引就能夠返回查詢所須要的數據,這樣避免了查到索引後再返回表操做,減小I/O提升效率。
例如表T中有一個普通索引 idx_key(key),那麼:
-- 索引覆蓋了
select id from T where key = 'test';
-- 索引沒覆蓋,須要回表
select * from T where key = 'test';
複製代碼
問題,爲何第一個SQL索引覆蓋了? 非聚簇索引的葉子節點存的是id。
惟一索引和普通索引在查詢和更新的時候區別:
惟一索引找到知足的第一條記錄會立馬返回,通知檢索(由於惟一性的保證)。可是這個區別並無很大的性能區別,由於Innodb是按照頁(默認16KB)讀寫的,讀數據的時候是從B+樹的根節點開始搜索,搜索的時候將整個頁從硬盤加載到內存。
惟一索引在插入的時候會多作些判斷,想要作這個判斷就必須先把數據頁讀入內存。可是普通索引不須要作這個判斷,就能夠把須要更新的數據作判斷:若是數據在內存則直接更新;若是不在也不加載內存,而是先寫入change buffer,等下次查詢的時候再執行change buffer。這樣普通索引會相對性能好一些。可是注意:若是業務場景是寫入後立馬有查詢,其實仍是會立馬須要把數據頁加載到內存,這樣的狀況下其實並不能帶來優化IO的操做。
Mysql 5.6 引入了全文索引Full text index,可是隻能適用於分詞的狀況,若是是匹配字符串的一部分就不適用了。
MySQL支持三種模式的全文檢索模式:天然語言模式(IN NATURAL LANGUAGE MODE),即經過MATCH AGAINST 傳遞某個特定的字符串來進行檢索。 布爾模式(IN BOOLEAN MODE),能夠爲檢索的字符串增長操做符,例如「+」表示必須包含,「-」表示不包含,「*」表示通配符(這種狀況, 即便傳遞的字符串較小或出如今停詞中,也不會被過濾掉),其餘還有不少特殊的布爾操做符,能夠經過以下參數控制: 查詢擴展模式(WITH QUERY EXPANSION), 這種模式是天然語言模式下的一個變種,會執行兩次檢索,第一次使用給定的短語進行檢索,第二次是結合第一次相關性比較高的行進行檢索。
對於一個表裏的多個列,好比是有些列高頻查詢,有些列低頻查詢。若是爲每個低頻的列單獨創建索引感受有些浪費,若是不創建索引又只能走全表掃描。因此咱們常常用聯合索引來解決這個問題,聯合索引如idx_key1_key2_key3(key1,key2,key3)
,至關於建立了(key1)
、(key1,key2)
和(key1,key2,key3)
三個索引,那麼在創建聯合索引的時候,如何安排索引內的字段順序?
咱們考慮key1 是最經常使用的列放最前面,key2和key3不經常使用。
上面這種創建一個聯合索引就實際上包含了3個索引的特性就是最左匹配原則
。這個最左匹配能夠是聯合索引的最左 N 個字段,也能夠是字符串索引的最左 M 個字符。
總結起來
在MySQL 5.6中,引入了Index Condition Pushdown Optimization 優化。本質是針對那些須要回表查找的部分若是索引裏已經包含了該列,那麼先在索引裏作過濾判斷。
以用戶表的聯合索引(name, age)爲例。若是如今有一個需求:檢索出表中「名字第一個字是張,並且年齡是 10 歲的全部男孩」。那麼,SQL 語句是這麼寫的:
mysql> select * from tuser where name like '張 %' and age=10 and ismale=1;
複製代碼
咱們已經知道了前綴索引規則,因此這個語句在搜索索引樹的時候,只能用 「張」,找到第一個知足條件的記錄 ID3。固然,這還不錯,總比全表掃描要好。 而後呢? 固然是判斷其餘條件是否知足。 在 MySQL 5.6 以前,只能從 ID3 開始一個個回表。到主鍵索引上找出數據行,再對比字段值。 而 MySQL 5.6 引入的索引下推優化(index condition pushdown), 能夠在索引遍歷過程當中,對索引中包含的字段先作判斷,直接過濾掉不知足條件的記錄,減小回表次數。
出如今 SELECT、UPDATE、DELETE 語句的 WHERE 從句中的列
包含在 ORDER BY、GROUP BY、DISTINCT 中的字段
並不要將符合 1 和 2 中的字段的列都創建一個索引, 一般將 一、2 中的字段創建聯合索引效果更好
多表 join 的關聯列
force index
語句來優化)
結合B+Tree的特色,自增主鍵是連續的,在插入過程當中儘可能減小頁分裂,即便要進行頁分裂,也只會分裂不多一部分。而且能減小數據的移動,每次插入都是插入到最後。總之就是減小分裂和移動的頻率。
因爲InnoDB索引的特性,所以若是主索引不是自增的(id做主鍵),那麼每次插入新的數據,都極可能對B+Tree的主索引進行重整,影響性能。所以,儘可能以自增id做爲InnoDB的主索引。
這就是爲何咱們在《分佈式ID總結》裏提到主鍵的Id需求通常是總體趨勢遞增的緣由。
每一個非主鍵索引的葉子節點上都是主鍵的值。若是用UUID,好比 b8a52179-7d54-46de-b1de-d88911a42790
作主鍵,那麼每一個二級索引的葉子節點佔用約 36字節,而若是用整型作主鍵,則只要 4字節,若是是長整型(bigint)則是 8字節。因此,主鍵長度越小,普通索引的葉子節點就越小,普通索引佔用的空間也就越小。
在《Snowflake分佈式ID服務》 裏利用了twitter的雪花算法來儘可能作到生成短數字
且趨勢自增
的的ID。
限制表上的索引數目。對一個存在大量更新操做的表,所建索引的數目通常不要超過3個,最多不要超過5個。 索引雖然說提升了訪問速度,但太多索引會影響數據的更新操做。
對複合索引,按照字段在查詢條件中出現的頻度創建索引。在複合索引中,記錄首先按照第一個字段排序。 對於在第一個字段上取值相同的記錄,系統再按照第二個字段的取值排序,以此類推。 所以只有複合索引的第一個字段出如今查詢條件中,該索引纔可能被使用,所以將應用頻度高的字段,放置在複合索引的前面,會使系統最大可能地使用此索引,發揮索引的做用。
ALTER TABLE `T` ADD `reverse_identifier` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci;
select * from T where reverse_identifier like reverse('%SDTE');
複製代碼
推薦閱讀:《阿里巴巴Java開發手冊》索引規約章節 和 極客時間《MySQL實戰45講》。
我買極客時間《MySQL實戰45講》課程挺久了,利用上下班路上時間零散的看。每次都受益不淺,從丁奇的課程中又鞏固深刻了大學時候學的數據庫知識,推薦對數據庫細節有興趣的朋友。