1. 程式人生 > >從認識索引到理解索引「索引優化」

從認識索引到理解索引「索引優化」

認識索引

認識索引是什麼東西非常關鍵,一個非常恰當的比喻就是書的目錄頁與書的正文內容之間的關係,為了方便查詢書中的內容,通過對內容建立索引形成目錄。因此,首先你要明白的一點就是,索引它也是一個檔案,它是要佔據物理空間的。

比如對於MyISAM儲存引擎來說:

.frm字尾的檔案儲存的是表結構。

.myd字尾的檔案儲存的是表資料。

.myi字尾的檔案儲存的就是索引檔案。

如下圖所示:

對於InnoDB儲存引擎來說:

.frm

.ibd字尾的檔案存放索引檔案和資料(需要開啟innodb_file_per_table引數)

如下圖所示:

因此,當你對一張表建立索引時,索引檔案的大小也會改變,當你資料表中的資料因為增刪改變化時,索引檔案也會變化的,只不過MySQL會自動維護索引,這個過程不需要你介入,這也是為什麼不恰當的索引會影響MySQL效能的原因。

總結:

1. 索引是按照特定的資料結構把資料表中的資料放在索引檔案中,以便於快速查詢;

2. 索引存在於磁碟中,會佔據物理空間。

索引的型別

B-Tree 索引

以 B-Tree 為結構的索引是最常見的索引型別,比如 InnoDB 和 MyISAM 都是以 B-Tree 為索引結構的索引,事實上是以 B+ Tree 為索引結構,B-Tree 和 B+Tree 區別在於,B+ Tree 在葉子節點上增加了順序訪問指標,方便葉子節點的範圍遍歷。這裡主要介紹一下 InnoDB 和 MyISAM。

InnoDB

InnoDB 支援聚簇索引,聚簇索引和非聚簇索引嚴格來說不是一種索引,而是一種資料儲存方式,這個名字跟它本身的儲存方式有關係,“聚簇“表示資料行和相鄰的鍵值儲存在一起,簡單的說,就是葉子節點中儲存的實際是真實的資料。InnoDB 通過主鍵聚集資料,所以一個表只能有一個聚簇索引,且必須有主鍵,如果沒有定義主鍵,且不存在非空索引可以代替,InnoDB 會隱式定義一個主鍵作為聚簇索引。

聚簇索引的二級索引儲存的不是指向行的物理位置的指標,而是行的主鍵值,所以如果通過二級索引查詢行,需要找到二級索引的葉子結點獲得對應的主鍵值,然後再去查詢對應的行。對於 InnoDB,自適應雜湊索引可以減少這樣的重複工作。

InnoDB 使用的是行鎖,所以支援事務,而 MyISAM 使用的是表鎖,不支援事務。

適用範圍

B-Tree 索引適用於區間查詢,因為 B-Tree 儲存後的葉子節點本身就是有序的,並且 B+ Tree 結構還增加了葉子節點的連續順序指標,對於區間查詢來說就更加方便了。

雜湊索引

雜湊索引是基於雜湊表實現的,只有精確匹配索引所有列的查詢才有效。方法是,對所有的索引列計算一個 hash code,hash code 作為索引,在雜湊表中儲存指向每個資料行的指標。

優點

索引本身只儲存 hash code,所以結構很緊湊,並且查詢速度很快

限制

索引中的 hash code 是順序儲存的,但是 hash code 對應的資料並不是順序的,所以無法用於排序

不支援部分索引列匹配查詢,因為雜湊索引是使用索引列的全部內容來計算 hash code

只支援等值比較,不支援範圍查詢

如果雜湊衝突嚴重時,必須遍歷連結串列中所有行指標

雜湊衝突嚴重的話,索引維護操作的代價也很高

InnoDB 的自適應雜湊索引

首先,請注意,自適應雜湊索引對於使用者來說是無感知的,這是一個完全自動、內部的行為,使用者無法控制或者配置,但是可以關閉。

當 InnoDB 注意到某個索引值被使用的非常頻繁時,它會在記憶體中基於 B-Tree 索引之上再建立一個雜湊索引,這樣 B-Tree 也可以具有雜湊索引的一些優點,比如快速的雜湊查詢。

當然如果儲存引擎不支援雜湊索引,使用者也可以自定義雜湊索引,這樣效能會比較高,缺陷是需要自己維護雜湊值,如果採用這種方法,不要使用 SHA1() 和 MD5() 作為雜湊函式,因為這兩個是強加密函式,設計目標是最大限度消除衝突,生成的 hash code 是一個非常長的字串,浪費大量的空間,雜湊索引中對於索引的衝突要求沒有那麼高。

索引的優點

使用索引可以減少伺服器需要掃描的資料量

使用索引可以幫助伺服器避免排序和臨時表

使用索引可以將隨機 I/O 變為順序 I/O

但是不是所有情況下,索引都是最好的解決方案,對於非常小的表來說,大部分情況下簡單的全表掃描更高效,對於中到大型表,索引就比較有效,對於特大型的表來說,分割槽會更加有效。

常見優化方法

聯合索引最左字首原則

複合索引遵守「最左字首」原則,查詢條件中,使用了複合索引前面的欄位,索引才會被使用,如果不是按照索引的最左列開始查詢,則無法使用索引。

比如在(a,b,c)三個欄位上建立聯合索引,那麼它能夠加快a|(a,b)|(a,b,c)三組查詢的速度,而不能加快b|(b,a)這種查詢順序。

另外,建聯合索引的時候,區分度最高的欄位在最左邊。

不要在列上使用函式和進行運算

不要在列上使用函式,這將導致索引失效而進行全表掃描。

例如下面的 SQL 語句:

select * from artile where YEAR(create_time) <= '2018'; 複製程式碼

即使 date 上建立了索引,也會全表掃描,可以把計算放到業務層,這樣做不僅可以節省資料庫的 CPU,還可以起到查詢快取優化效果。

負向條件查詢不能使用索引

負向條件有:!=、<>、not in、not exists、not like 等。

select * from artile where status != 1 and status != 2; 複製程式碼

可以使用in進行優化:

select * from artile where status in (0,3) 複製程式碼

使用覆蓋索引

所謂覆蓋索引,是指被查詢的列,資料能從索引中取得,而不用通過行定位符再到資料表上獲取,能夠極大的提高效能。

可以定義一個讓索引包含的額外的列,即使這個列對於索引而言是無用的。

避免強制型別轉換

當查詢條件左右兩側型別不匹配的時候會發生強制轉換,強制轉換可能導致索引失效而進行全表掃描。

如果phone欄位是varchar型別,則下面的SQL不能命中索引:

select * from user where phone=12345678901; 複製程式碼

可以優化為:

select * from user where phone='12345678901'; 複製程式碼

範圍列可以用到索引

範圍條件有:<、<=、>、>=、between等。

範圍列可以用到索引,但是範圍列後面的列無法用到索引,索引最多用於一個範圍列,如果查詢條件中有兩個範圍列則無法全用到索引。

更新頻繁、資料區分度不高的欄位上不宜建立索引

更新會變更B+樹,更新頻繁的欄位建立索引會大大降低資料庫效能。

「性別」這種區分度不大的屬性,建立索引沒有意義,不能有效過濾資料,效能與全表掃描類似。

區分度可以使用 count(distinct(列名))/count(*) 來計算,在80%以上的時候就可以建立索引。

索引列不允許為null

單列索引不存null值,複合索引不存全為null的值,如果列允許為 null,可能會得到不符合預期的結果集。

避免使用or來連線條件

應該儘量避免在 where 子句中使用 or 來連線條件,因為這會導致索引失效而進行全表掃描,雖然新版的MySQL能夠命中索引,但查詢優化耗費的 CPU比in多。

模糊查詢

前導模糊查詢不能使用索引,非前導查詢可以。

歡迎工作一到五年的Java工程師朋友們加入Java填坑之路:860113481

群內提供免費的Java架構學習資料(裡面有高可用、高併發、高效能及分散式、Jvm效能調優、Spring原始碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!