摘要:使用索引也很簡單,然而, 會使用索引是一回事, 而深入理解索引原理又能恰到好處使用索引又是另一回事。
本文分享自華為雲社區《索引到底能提升多少查詢效率?何時該使用索引?一文快速搞懂數據庫索引及合理使用它》,作者: 曲鳥。
一、前言
無論是面試、還是日常工作中,或多或少都會使用或者聽到別人談論索引這個技術。
然而很大一部份程序員對索引的了解僅限於到“加索引能使查詢變快”這個概念為止。
使用索引也很簡單,然而, 會使用索引是一回事, 而深入理解索引原理又能恰到好處使用索引又是另一回事。
這已經是兩個相差甚遠的技術層級了。
二、千萬級數據表索引和無索引查詢效率對比
現在有一個學生表student,有1000萬條數據
未加索引,查詢class_id=2的學生信息的耗時:SELECT * FROM student WHERE class_id=2 花費了3.357秒
加上索引,查詢class_id=2的學生信息的耗時:SELECT * FROM student WHERE class_id=2 花費了0.017秒
1000萬條數據下,兩個查詢的性能差了近200倍!!
這個差距是特別大的! 難怪需要加索引!!!
三、什么是索引
網上很多講解索引的文章對索引的描述是這樣的:
索引就像書的目錄, 通過書的目錄就可以准確的定位到書籍的具體的內容。
這句話概述的非常正確!
但說了跟沒說一樣,懂的人自然懂!不懂的人感覺懂了,但還是一臉蒙的狀態!
其實想要理解索引原理,必須清楚一種數據結構:
平衡樹」(非二叉),也就是b tree或者 b+ tree
當然, 有的數據庫也使用哈希桶作用索引的數據結構 , 然而, 主流的RDBMS都是把平衡樹當做數據表默認的索引數據結構的。
我們平時建表的時候都會為表加上主鍵, 在某些關系數據庫中, 如果建表時不指定主鍵,數據庫會拒絕建表的語句執行。
事實上, 一個加了主鍵的表,並不能被稱之為“表”。一個沒加主鍵的表,它的數據無序的放置在磁盤存儲器上,一行一行的排列的很整齊。如果給表上了主鍵,那么表在磁盤上的存儲結構就由整齊排列的結構轉變成了樹狀結構,也就是上面說的“平衡樹”結構,換句話說,就是整個表就變成了一個索引。
沒錯, 再說一遍, 整個表變成了一個索引!
也就是所謂的“聚集索引”。 這就是為什么一個表只能有一個主鍵, 一個表只能有一個“聚集索引”,因為主鍵的作用就是把“表”的數據格式轉換成“樹(索引)”的格式。
未加索引時,之前執行的查詢sql會讓數據庫系統逐行的遍歷整張表,對於每一行都要檢查其class_id字段是否等於“2”。因為我們要查找所有class_id為“2”的員工,所以當我們發現了一條class_id是“2”的記錄后,並不能停止繼續查找,因為可能還有class_id等於“2”的其他記錄。
這就意味着,對於表中的千萬條記錄,數據庫每一條都要檢查。這就是所謂的“全表掃描”( full table scan)
而加上索引的最大作用就是加快查詢速度,它能從根本上減少需要掃表的記錄/行的數量。
四、Mysql中的索引
在MySQL中, 索引有兩種分類方式:邏輯分類和物理分類。
按照邏輯分類,索引可分為:
主鍵索引:一張表只能有一個主鍵索引,不允許重復、不允許為 NULL;
唯一索引:數據列不允許重復,允許為 NULL 值,一張表可有多個唯一索引,但是一個唯一索引只能包含一列,比如身份證號碼、卡號等都可以作為唯一索引;
普通索引:一張表可以創建多個普通索引,一個普通索引可以包含多個字段,允許數據重復,允許 NULL 值插入;
全文索引:讓搜索關鍵詞更高效的一種索引。
按照物理分類,索引可分為:
聚集索引:一般是表中的主鍵索引,如果表中沒有顯示指定主鍵,則會選擇表中的第一個不允許為 NULL 的唯一索引,如果還是沒有的話,就采用 Innodb 存儲引擎為每行數據內置的 6 字節 ROWID 作為聚集索引。每張表只有一個聚集索引,因為聚集索引的鍵值的邏輯順序決定了表中相應行的物理順序。聚集索引在精確查找和范圍查找方面有良好的性能表現(相比於普通索引和全表掃描),聚集索引就顯得彌足珍貴,聚集索引選擇還是要慎重的(一般不會讓沒有語義的自增 id 充當聚集索引);
非聚集索引:該索引中索引的邏輯順序與磁盤上行的物理存儲順序不同(非主鍵的那一列),一個表中可以擁有多個非聚集索引。
在目前用的最多的mysql的InnoDB存儲引擎中,是使用B+Tree索引方法來進行索引建立的。
B+樹索引是B+樹在數據庫中的一種實現,是最常見也是數據庫中使用最為頻繁的一種索引。
B+樹中的B代表平衡(balance),而不是二叉(binary),因為B+樹是從最早的平衡二叉樹演化而來的。先了解二叉查找樹、平衡二叉樹(AVLTree)和平衡多路查找樹(B-Tree),B+樹即由這些樹逐步優化而來。
具體的講解可參考:https://www.cnblogs.com/wuzhenzhao/p/10341114.html 該博客。
五、索引的優缺點
優點:
1、索引能夠提高數據檢索的效率,降低數據庫的IO成本。
2、通過創建唯一性索引,可以保證數據庫表中每一行數據的唯一性,創建唯一索引
3、在使用分組和排序子句進行數據檢索時,同樣可以顯著減少查詢中分組和排序的時間
4、加速兩個表之間的連接,一般是在外鍵上創建索引
缺點:
1、創建索引和維護索引要耗費時間,這種時間隨着數據量的增加而增加
2、索引需要占物理空間,除了數據表占數據空間之外,每一個索引還要占一定的物理空間,如果要建立聚簇索引,那么需要的空間就會更大
3、當對表中的數據進行增加、刪除和修改的時候,索引也要動態的維護,降低了數據的維護速度
六、索引何時應該使用
需創建索引的情況:
1.主鍵,自動建立唯一索引
2.頻繁作為查詢的條件的字段
3.查詢中與其他表關聯的字段存在外鍵關系
4.查詢中排序的字段,排序字段若通過索引去訪問將大大提高排序的速度
5.查詢中統計或者分組字段
避免創建索引的情況:
1.數據唯一性差的字段不要使用索引
比如性別,只有兩種可能數據。意味着索引的二叉樹級別少,多是平級。這樣的二叉樹查找無異於全表掃描。
2.頻繁更新的字段不要使用索引
比如登錄次數,頻繁變化導致索引也頻繁變化,增大數據庫工作量,降低效率。
3.字段不在where語句出現時不要添加索引
只有在where語句出現,mysql才會去使用索引
4.數據量少的表不要使用索引
使用了改善也不大
七、哪些sql能命中索引
1.前導模糊查詢不能使用索引, 如name like ‘%濤’
2、Union、in、or可以命中索引,建議使用in。
3、負條件查詢不能使用索引,可以優化為in查詢,其中負條件有!=、<>、not in、not exists、not like等
4、聯合索引最左前綴原則,又叫最左側查詢,如果在(a,b,c)三個字段上建立聯合索引,那么它能夠加快a|(a,b)|(a,b,c)三組的查詢速度。
5、建立聯合查詢時,區分度最高的字段在最左邊
6、如果建立了(a,b)聯合索引,就不必再單獨建立a索引。同理,如果建立了(a,b,c)索引就不必再建立a,(a,b)索引
7、存在非等號和等號混合判斷條件時,在建索引時,要把等號條件的列前置
8、范圍列可以用到索引,但是范圍列后面的列無法用到索引。
索引最多用於一個范圍列,如果查詢條件中有兩個范圍列則無法全用到索引。范圍條件有:<、<=、>、>=、between等。
9、把計算放到業務層而不是數據庫層。在字段上計算不能命中索引,
10、強制類型轉換會全表掃描,
如果phone字段是varcher類型,則下面的SQL不能命中索引。Select * fromuser where phone=13800001234
11、更新十分頻繁、數據區分度不高的字段上不宜建立索引。
更新會變更B+樹,更新頻繁的字段建立索引會大大降低數據庫性能。
“性別”這種區分度不太大的屬性,建立索引是沒有什么意義的,不能有效過濾數據,性能與全表掃描類似。
一般區分度在80%以上就可以建立索引。區分度可以使用count(distinct(列名))/count(*)來計算。
12、利用覆蓋索引來進行查詢操作,避免回表。
被查詢的列,數據能從索引中取得,而不是通過定位符row-locator再到row上獲取,即“被查詢列要被所建的索引覆蓋”,這能夠加速度查詢。
13、建立索引的列不能為null,使用not null約束及默認值
14、利用延遲關聯或者子查詢優化超多分頁場景,
MySQL並不是跳過offset行,而是取offset+N行,然后放棄前offset行,返回N行,那當offset特別大的時候,效率非常低下,要么控制返回的總數,要么對超過特定閾值的頁進行SQL改寫。
15、業務上唯一特性的字段,即使是多個字段的組合,也必須建成唯一索引。
16、超過三個表最好不要用join,需要join的字段,數據類型必須一致,多表關聯查詢時,保證被關聯的字段需要有索引。
17、如果明確知道查詢結果只要一條,limit 1能夠提高效率,比如驗證登錄的時候。
18、Select語句務必指明字段名稱
19、如果排序字段沒有用到索引,就盡量少排序
20、盡量用union all 代替 union。Union需要將集合合並后在進行唯一性過濾操作,這會涉及到排序,大量的cpu運算,加大資源消耗及延遲,當然,使用union all的前提條件是兩個結果集沒有重復數據。
八、總結
索引是非常重要的技術!
但每建立一個索引,實際上都需要在硬盤上開辟一塊空間用於存儲這個索引所需要的數據結構(雖然表述不太准確但是是這個意思),因此不建議對太長的字段建立索引。
而且建立的索引並不是越多越好,因為索引雖然能夠提高查詢效率,但是會大大得影響插入、刪除和修改的效率,因為每一次數據的更新都會牽涉到對索引的修改。
綜上所述,往往在對於大量數據的插入的情況的時候,我們需要先刪除掉數據表的索引,等插入完畢后重新建立索引,這樣才能最大限度地保證數據庫的效率!