一、索引
在之前,我對索引有以下的認知:
- 索引可以加快數據庫的檢索速度
- 表經常進行
INSERT/UPDATE/DELETE
操作就不要建立索引了,換言之:索引會降低插入、刪除、修改等維護任務的速度。 - 索引需要佔物理和數據空間。
- 瞭解過索引的最左匹配原則
- 知道索引的分類:聚集索引和非聚集索引
- Mysql支持Hash索引和B+樹索引兩種
看起來好像啥都知道,但面試讓你說的時候可能就GG了:
- 使用索引爲什麼可以加快數據庫的檢索速度啊?
- 爲什麼說索引會降低插入、刪除、修改等維護任務的速度。
- 索引的最左匹配原則指的是什麼?
- Hash索引和B+樹索引有什麼區別?主流的使用哪一個比較多?InnoDB存儲都支持嗎?
- 聚集索引和非聚集索引有什麼區別?
- ........
1.1聊聊索引的基礎知識
首先Mysql的基本存儲結構是頁(記錄都存在頁裏邊):
- 各個數據頁可以組成一個雙向鏈表
- 而每個數據頁中的記錄又可以組成一個單向鏈表
- 每個數據頁都會爲存儲在它裏邊兒的記錄生成一個頁目錄,在通過主鍵查找某條記錄的時候可以在頁目錄中使用二分法快速定位到對應的槽,然後再遍歷該槽對應分組中的記錄即可快速找到指定的記錄
- 以其他列(非主鍵)作爲搜索條件:只能從最小記錄開始依次遍歷單鏈表中的每條記錄。
所以說,如果我們寫select * from user where username = 'Java3y'
這樣沒有進行任何優化的sql語句,默認會這樣做:
- 定位到記錄所在的頁
- 需要遍歷雙向鏈表,找到所在的頁
- 從所在的頁內中查找相應的記錄
- 由於不是根據主鍵查詢,只能遍歷所在頁的單鏈表了
很明顯,在數據量很大的情況下這樣查找會很慢!
1.2索引提高檢索速度
索引做了些什麼可以讓我們查詢加快速度呢?
其實就是將無序的數據變成有序(相對):
要找到id爲8的記錄簡要步驟:
很明顯的是:沒有用索引我們是需要遍歷雙向鏈表來定位對應的頁,現在通過**“目錄”**就可以很快地定位到對應的頁上了!
其實底層結構就是B+樹,B+樹作爲樹的一種實現,能夠讓我們很快地查找出對應的記錄。
參考資料:
1.3索引降低增刪改的速度
B+樹是平衡樹的一種。
平衡樹:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。
如果一棵普通的樹在極端的情況下,是能退化成鏈表的(樹的優點就不復存在了)
B+樹是平衡樹的一種,是不會退化成鏈表的,樹的高度都是相對比較低的(基本符合矮矮胖胖(均衡)的結構)【這樣一來我們檢索的時間複雜度就是O(logn)】!從上一節的圖我們也可以看見,建立索引實際上就是建立一顆B+樹。
- B+樹是一顆平衡樹,如果我們對這顆樹增刪改的話,那肯定會破壞它的原有結構。
- 要維持平衡樹,就必須做額外的工作。正因爲這些額外的工作開銷,導致索引會降低增刪改的速度
B+樹刪除和修改具體可參考:
1.4哈希索引
除了B+樹之外,還有一種常見的是哈希索引。
哈希索引就是採用一定的哈希算法,把鍵值換算成新的哈希值,檢索時不需要類似B+樹那樣從根節點到葉子節點逐級查找,只需一次哈希算法即可立刻定位到相應的位置,速度非常快。
- 本質上就是把鍵值換算成新的哈希值,根據這個哈希值來定位。
看起來哈希索引很牛逼啊,但其實哈希索引有好幾個侷限(根據他本質的原理可得):
- 哈希索引也沒辦法利用索引完成排序
- 不支持最左匹配原則
- 在有大量重複鍵值情況下,哈希索引的效率也是極低的---->哈希碰撞問題。
- 不支持範圍查詢
參考資料:
- www.cnblogs.com/zengkefu/p/…---hash索引和b+tree索引
1.5InnoDB支持哈希索引嗎?
主流的還是使用B+樹索引比較多,對於哈希索引,InnoDB是自適應哈希索引的(hash索引的創建由InnoDB存儲引擎引擎自動優化創建,我們干預不了)!
參考資料:
1.6聚集和非聚集索引
簡單概括:
- 聚集索引就是以主鍵創建的索引
- 非聚集索引就是以非主鍵創建的索引
區別:
- 聚集索引在葉子節點存儲的是表中的數據
- 非聚集索引在葉子節點存儲的是主鍵和索引列
- 使用非聚集索引查詢出數據時,拿到葉子上的主鍵再去查到想要查找的數據。(拿到主鍵再查找這個過程叫做回表)
非聚集索引也叫做二級索引,不用糾結那麼多名詞,將其等價就行了~
非聚集索引在建立的時候也未必是單列的,可以多個列來創建索引。
- 此時就涉及到了哪個列會走索引,哪個列不走索引的問題了(最左匹配原則-->後面有說)
- 創建多個單列(非聚集)索引的時候,會生成多個索引樹(所以過多創建索引會佔用磁盤空間)
在創建多列索引中也涉及到了一種特殊的索引-->覆蓋索引
- 我們前面知道了,如果不是聚集索引,葉子節點存儲的是主鍵+列值
- 最終還是要“回表”,也就是要通過主鍵再查找一次。這樣就會比較慢
- 覆蓋索引就是把要查詢出的列和索引是對應的,不做回表操作!
比如說:
- 現在我創建了索引
(username,age)
,在查詢數據的時候:select username , age from user where username = 'Java3y' and age = 20
。 - 很明顯地知道,我們上邊的查詢是走索引的,並且,要查詢出的列在葉子節點都存在!所以,就不用回表了~
- 所以,能使用覆蓋索引就儘量使用吧~
1.7索引最左匹配原則
最左匹配原則:
- 索引可以簡單如一個列
(a)
,也可以複雜如多個列(a, b, c, d)
,即聯合索引。 - 如果是聯合索引,那麼key也由多個列組成,同時,索引只能用於查找key是否存在(相等),遇到範圍查詢
(>、<、between、like
左匹配)等就不能進一步匹配了,後續退化爲線性查找。 - 因此,列的排列順序決定了可命中索引的列數。
例子:
- 如有索引
(a, b, c, d)
,查詢條件a = 1 and b = 2 and c > 3 and d = 4
,則會在每個節點依次命中a、b、c,無法命中d。(很簡單:索引命中只能是相等的情況,不能是範圍匹配)
1.8=、in自動優化順序
不需要考慮=、in等的順序,mysql會自動優化這些條件的順序,以匹配儘可能多的索引列。
例子:
- 如有索引
(a, b, c, d)
,查詢條件c > 3 and b = 2 and a = 1 and d < 4
與a = 1 and c > 3 and b = 2 and d < 4
等順序都是可以的,MySQL會自動優化爲a = 1 and b = 2 and c > 3 and d < 4
,依次命中a、b、c。
1.9索引總結
索引在數據庫中是一個非常重要的知識點!上面談的其實就是索引最基本的東西,要創建出好的索引要顧及到很多的方面:
- 1,最左前綴匹配原則。這是非常重要、非常重要、非常重要(重要的事情說三遍)的原則,MySQL會一直向右匹配直到遇到範圍查詢
(>,<,BETWEEN,LIKE)
就停止匹配。 - 3,儘量選擇區分度高的列作爲索引,區分度的公式是
COUNT(DISTINCT col) / COUNT(*)
。表示字段不重複的比率,比率越大我們掃描的記錄數就越少。 - 4,索引列不能參與計算,儘量保持列“乾淨”。比如,
FROM_UNIXTIME(create_time) = '2016-06-06'
就不能使用索引,原因很簡單,B+樹中存儲的都是數據表中的字段值,但是進行檢索時,需要把所有元素都應用函數才能比較,顯然這樣的代價太大。所以語句要寫成 :create_time = UNIX_TIMESTAMP('2016-06-06')
。 - 5,儘可能的擴展索引,不要新建立索引。比如表中已經有了a的索引,現在要加(a,b)的索引,那麼只需要修改原來的索引即可。
- 6,單個多列組合索引和多個單列索引的檢索查詢效果不同,因爲在執行SQL時,MySQL只能使用一個索引,會從多個單列索引中選擇一個限制最爲嚴格的索引。
參考資料:
- zhuanlan.zhihu.com/p/23624390--簡單理解索引
- blog.csdn.net/mysteryhaoh…-- MySQL學習之——索引(普通索引、唯一索引、全文索引、索引匹配原則、索引命中等)
- monkeysayhi.github.io/2018/03/06/…---淺談MySQL的B樹索引與索引優化