索引基礎知識總結

萌新J發表於2020-11-26

定義

  一句話總結,索引是一個排好序的用於快速查詢的資料結構。這句話說明了索引的三個特點,第一個是有序的,已經將索引列資料排好序了;第二個是快速查詢,這就意味著使用索引可以快速定位到符合條件的資料;第三個是一個資料結構。我們平時使用 SQL 語句查詢資料時,比如執行 select * from student where name = "張三" ;那麼它會去儲存資料的磁碟上一行一行進行 name 欄位的判斷,如果符合就返回,不符合就跳過,這樣在資料量較大時查詢效率就很低,所以索引的使用就可以在資料量很大時快速定位到資料。因為索引也是需要佔用磁碟空間的,所以如果要查的資料量並不是很大時就沒有必要使用索引,否則效率會沒有提升甚至降低,而且還浪費了磁碟空間。

 

結構

  索引共有三種資料結構,Hash、B樹,B+ 樹。其中 B+ 樹是預設的,也是使用最廣泛的。

Hash

  hash 結構就是以雜湊表的方式進行儲存。

優點:資料量小時,發生 hash 衝突的次數很少,查詢效率是非常高,因為 hash 衝突次數少大部分資料都儲存在雜湊表的陣列下標上,並且如果是鏈地址法解決雜湊衝突的話產生的連結串列長度也不會太長,而陣列的資料因為記憶體連續所以查詢效率自然就高。

缺點:1、資料量大時,hash 衝突的次數就變多,形成連結串列的長度也會變長,而連結串列遍歷是低效的,所以查詢資料就會很慢;2、因為 hash 表是沒有順序的,所以不能進行一些範圍查詢例如 >,< 等,也不能進行 like 模糊查詢。

絕大多數情況使用的索引都是 B+ 樹或 B 樹結構的,所以開頭說索引的定義是排好序的快速查詢結構就排除了 Hash 結構的索引。

 

B樹

B 樹是多叉樹的一種,相比於二叉樹它的特點是每個父節點可能包含多個子節點,而它的資料也是儲存在各個節點上。每個節點的左子結點小於當前節點所攜帶資料的值,右子節點大於當前節點的值。每次查詢從根節點出發進行判斷。

  優點:有序,查詢高效,尤其是靠近根節點的資料比如上圖中的 20 ,40會很快查到。

  缺點:遍歷效率低,遍歷時每次都需要從根節點出發查詢每個值所在的位置。

 

B+樹(預設)

 B+樹相比於 B 樹改善了遍歷效率,它是將資料統一儲存在葉子節點上,並且對於相鄰的葉子節點以連結串列的形式連線,這樣在遍歷時就不需要每次都從根節點開始。

優點:遍歷速度快,查詢速度快。 

缺點:單個資料查詢時效率不一定有 B 樹高。

 

 

不同儲存引擎下的實現

上面說得是索引的基本結構,因為一般都是使用 B+ 樹實現的,所以這裡也用 B+ 來講解在 MyISAM 與 InnoDB 中的不同實現。

MyISAM

在 MyISAM 中,主鍵索引和非主鍵索引的結構是一樣,葉子節點儲存的都是最終行資料所在的磁碟地址,當找到符合的條件地址後需要去磁碟地址上找到對應的行資料。

索引和資料儲存:索引和資料是用兩種檔案格式儲存的,資料使用的是 .MYD結尾的檔案儲存的,而索引是使用 .MYI結尾的檔案儲存的(.frm 是表結構檔案)。  資料檔案和配圖轉自【MySQL】索引原理(二):B+Tree索引的實現,MyISAM和InnoDB 。

不足:5.6之前的 MyISAM 相比於 InnoDB 多了全文索引,同時內部維護了一個計數器,對於 conut 函式可以直接讀取(不含where等條件篩選時)。模糊查詢效率會比 InnoDB 高。但是其不支援事務,沒有行級鎖,同時 5.6之後 InnoDB 也引入了全文索引,所以 MyISAM 慢慢被遺棄了。

 

InndDB(預設)

在 InnoDB 中將索引分為聚簇索引與非聚簇索引。

聚簇索引預設是該表主鍵對應的索引;如果該表沒有主鍵,那麼會從該表中選擇一個唯一非空的列作為聚簇索引;而如果表中也沒有唯一非空的列,那麼就會 InnoDB 就會自己維護一個聚簇索引,但是這樣遍歷的效率就會低一些。

非聚簇索引指得就是除聚簇索引外的其他索引。

 

之所以這樣分是因為聚簇索引的結構與非聚簇索引的結構不同,非聚簇索引結構的 B+樹葉子結點儲存的是主鍵值和當前的索引列的值。而聚簇索引結構的 B+樹葉子結點儲存的就是對應的行資料。如果使用聚簇索引進行查詢只需要在一個索引結構中進行查詢就可以得到這一行的所有資料,所以在 InnoDB 中使用主鍵查詢資料效率非常高;而如果使用非聚簇索引進行查詢那麼在查出主鍵值後還需要去主鍵所在的索引結構中進行回表查詢(沒有發生索引覆蓋時),也就是比聚簇索引查詢多了一次查詢。

索引和資料儲存:InnoDB 索引的資料因為直接儲存在聚簇索引所在的 B+ 樹中,所以只有一種格式的檔案,檔案以 .ibd 結尾。

Innodb 結構的主鍵推薦設定為自增的主鍵,因為 如果設定為 UUID 的,那麼每次插入都可能會在原有的資料之間,因為葉子結點之間是以連結串列形式連線的,所以在中間的插入會比兩邊插入慢,導致操作效率下降。

 

種類

1、主鍵索引(Primary key)。一張表的主鍵預設就是一個主鍵索引。

2、唯一索引(Unique Index)。某一列的值是唯一的可以使用唯一索引。

3、普通索引(Index(列名))。

4、全文索引(Fulltext Index)。5.6 之前是 MyISAM 專屬的,5.6 開始 InnoDB 也引入了,適用於在資料量大的場景進行模糊查詢。

5、聯合索引(Index(列名,列名))。為多列設定一個索引,這些列在篩選時都可以用到這個索引,提升效率,同時減小了索引的佔用空間。聯合索引的使用必須遵守最左匹配原則,否則相應的列索引就會失效。

最左匹配原則:在查詢時會先從聯合索引的最左列開始匹配,如果某列沒有出現或者出現了範圍查詢,那麼後面的列都不會用到索引。

注意:1、這裡的範圍查詢是指 >、>=、<、<=,如果是 like,那麼只要它的開頭不是 "%"、"_"  萬用字元就符合最左匹配原則,而如果是萬用字元開頭的,那麼它就和 in、not in 一樣失效,後面的列也會失效。比如聯合索引(name ,age),where name like '張%' and age =20;這個name 與age 都會用到索引,而如果是 where name like '%三' and age =20;那麼 name 與 age 都不會用到索引。

      2、and 條件的列可以改變位置,比如對於聯合索引(name,age),那麼where age>20 and name like '張%'; 在mysql 解析時會將 name 優化到 age 條件的前面保持最左匹配原則,age 與 name 都會用到索引。

6、字首索引(Index(列名(擷取長度)))。對於某個欄位長度過長,同時前幾位的資料段就可以代表當前值的重複水平。那麼就可以建立字首索引來提高檢索索引。首先查詢 select count(*)/count(distinct 列名),然後不斷調整引數 select count(*)/count(distinct 列名(擷取長度)),當結果無限接近於前面的值就取該長度作為字首索引的擷取欄位。

 

使用

操作

建立:1、create  索引型別  索引名  on  表名(列名),如 create  index  idx_name  on  student(name)     2、Alter  table  表名  add  索引型別  索引名(列名),如 alter  table  student  add  index  idx_name(name)

刪除:1、drop  index  索引名  on  表名,如 drop  index  idx_name  on  student      2、Alter  table  表名  drop  index  索引名,如 alter  table  student  drop  index  idx_name

對於主鍵只能先移除 "主鍵身份" 再刪除。 alter  table  表名  modify  主鍵列名   int; 然後執行刪除操作: Alter  table  表名  drop  index  primary key;。

 

使用場景

因為索引會佔用空間,所以需要滿足特定條件才推薦使用索引。

1、資料量大。資料量小不會提升效率甚至會降低效率。

2、讀取操作多,修改少。每次對索引列資料的修改都需要去修改索引對應的結構,如果一張表的修改次數大於讀取次數,那麼使用索引反倒會使程式總體的效率降低。

3、重複資料少,列資料種類多。如果對 sex 性別列建立索引,那麼索引的作用也不大甚至會降低效率。

 

失效場景

1、對索引列篩選值是該列所包含的大部分值時,就會優化成全表查詢,跳過索引來保證更好的效率。比如說 age 欄位都是大於0 的,而在 where 篩選條件為 age>0,那麼這個篩選就會被優化成全表查詢。

2、對索引列進行 is null、is not null 判斷可能會失效。當該列本身就設定為非空的,那麼由於1的作用is not null直接就被優化成全表查詢,索引失效,而is null 則會生效;如果列可以為空,那麼is null 和 is not null 還是會根據該列的null 值多少來判斷,如果null 值幾乎沒有,那麼和前面的情況一樣,如果幾乎都是 null 值,那麼is null 就會優化成全表掃描,而 is not null 則會用到索引,而兩者資料差不多則兩者都會用到索引。

3、對索引列使用函式或表示式

4、模糊查詢 like 時不以具體值為開頭,而是以 "%"、"_" 萬用字元開頭。

5、對索引列使用  !=、not in 。索引中儲存的是最終的資料或地址,如果是查詢不等於某個值必然是需要進行全表掃描的。

6、索引列進行了自動型別轉換。比如 phone 欄位型別是 varchar,而在查詢的時候沒有使用單引號, 那麼執行後也能得到最終結果,但是卻沒有走索引。

7、使用 or 如果左右條件有一方沒有用到索引時那麼用到索引的那一方索引也會失效

8、對於聯合索引沒有遵守最左匹配原則

 

相關文章