Mysql索引掃盲總結

Mysql索引掃盲總結

本文總結了一些MySQL索引的基本概念和原理,若是能夠快速清晰回答這些問題能夠出門左轉提提寶貴建議。mysql

什麼是索引?索引爲何查詢快,索引的數據結構是什麼? 聚簇索引/非聚簇索引區別? 什麼是覆蓋索引? 惟一索引/普通索引? 單列索引/聯合索引區別? Full-index全文索引? 什麼是下推索引? 什麼是最左匹配,查詢回表? 哪些字段適合建索引? 爲何通常主鍵索引最好是自增加的, 儘可能短的數值類型? 爲何有些SQL不走索引? 索引的最佳實踐?web

索引爲何快

索引的本質是空間換時間。算法

  • +bonus: 加快檢索速度,加快多表鏈接
  • -price: 額外空間開銷,維護索引的額外時間開銷

因此咱們經過索引這個緩存來提升數據查詢的效率。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的操做。

Full-index全文索引

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 個字符。

總結起來

  1. 索引的匹配規則是 左匹配
  2. 只有複合索引的第一個字段出如今查詢條件中,該索引纔可能被使用
  3. 有了(A,B,C),就等於同時擁有了(A),(A,B)和 (A,B,C) 三個索引
  4. 只要索引內,開始用範圍查詢,後面的索引就失效了。 **這裏注意:**IN 在 where 中,也屬於準確查詢,不會使後面索引失效。

什麼是下推索引?

在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), 能夠在索引遍歷過程當中,對索引中包含的字段先作判斷,直接過濾掉不知足條件的記錄,減小回表次數。

哪些字段適合建索引?

  1. 出如今 SELECT、UPDATE、DELETE 語句的 WHERE 從句中的列

  2. 包含在 ORDER BY、GROUP BY、DISTINCT 中的字段

  3. 並不要將符合 1 和 2 中的字段的列都創建一個索引, 一般將 一、2 中的字段創建聯合索引效果更好

  4. 多表 join 的關聯列

爲何有些SQL不走索引?

  1. 使用了通配符開頭,NOT IN 語句或者
  2. 聯合索引的第一個字段查詢條件中
  3. 數據引擎的優化器選錯了索引(能夠適當使用 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。

索引的最佳實踐?

要建索引

  1. 定義主鍵的數據列必定要創建索引。
  2. 定義有外鍵的數據列必定要創建索引。
  3. 對於常常查詢的數據列最好創建索引。
  4. 對於須要在指定範圍內的快速或頻繁查詢的數據列;
  5. 常常用在WHERE子句中的數據列。
  6. 常常出如今關鍵字order by、group by、distinct後面的字段,創建索引。若是創建的是複合索引,索引的字段順序要和這些關鍵字後面的字段順序一致,不然索引不會被使用。

不要建索引

  1. 對於那些查詢中不多涉及的列,重複值比較多的列不要創建索引。
  2. 對於定義爲text、image和bit的數據類型的列不要創建索引。
  3. 對於常常存取的列避免創建索引

索引的坑

  1. 限制表上的索引數目。對一個存在大量更新操做的表,所建索引的數目通常不要超過3個,最多不要超過5個。 索引雖然說提升了訪問速度,但太多索引會影響數據的更新操做。

  2. 對複合索引,按照字段在查詢條件中出現的頻度創建索引。在複合索引中,記錄首先按照第一個字段排序。 對於在第一個字段上取值相同的記錄,系統再按照第二個字段的取值排序,以此類推。 所以只有複合索引的第一個字段出如今查詢條件中,該索引纔可能被使用,所以將應用頻度高的字段,放置在複合索引的前面,會使系統最大可能地使用此索引,發揮索引的做用。

索引不會包含有NULL值的列

  1. 只要列中包含有NULL值都將不會被包含在索引中,複合索引中只要有一列含有NULL值,那麼這一列對於此複合索引就是無效的。 因此咱們在數據庫設計時不要讓字段的默認值爲NULL

使用短索引(列內容越短越好)

  1. 對列進行索引,若是可能應該指定一個前綴長度。 例如,若是有一個CHAR(255)的列,若是在前10個或20個字符內,多數值是唯一的,那麼就不要對整個列進行索引。 短索引不只能夠提升查詢速度並且能夠節省磁盤空間和I/O操做。

索引列排序

  1. MySQL查詢只使用一個索引,所以若是where子句中已經使用了索引的話,那麼order by中的列是不會使用索引的。 所以數據庫默認排序能夠符合要求的狀況下不要使用排序操做;儘可能不要包含多個列的排序,若是須要最好給這些列建立複合索引。

like語句操做

  1. 通常狀況下不鼓勵使用like操做,若是非使用不可,如何使用也是一個問題。like 「%aaa%」 不會使用索引,而like 「aaa%」可使用索引。即:左匹配規則。可使用reverse函數來支持逆序匹配,從而加強like走索引的可能。
ALTER TABLE `T` ADD `reverse_identifier` VARCHAR(255)  CHARACTER SET utf8  COLLATE utf8_general_ci;

select * from T where reverse_identifier like reverse('%SDTE');
複製代碼

不要在列上進行運算

  1. select * from users where YEAR(adddate)<2007; 將在每一個行上進行運算,這將致使索引失效而進行全表掃描,所以咱們能夠改爲 select * from users where adddate<‘2007-01-01’;

不使用NOT IN和<>操做

  1. 由於MySQL只對<,<=,=,>,>=,BETWEEN,IN,以及某些時候的LIKE纔會使用索引。 由於在以通配符 % 和 _ 開頭做查詢時,MySQL不會使用索引。

推薦閱讀:《阿里巴巴Java開發手冊》索引規約章節 和 極客時間《MySQL實戰45講》。

我買極客時間《MySQL實戰45講》課程挺久了,利用上下班路上時間零散的看。每次都受益不淺,從丁奇的課程中又鞏固深刻了大學時候學的數據庫知識,推薦對數據庫細節有興趣的朋友。

相關文章
相關標籤/搜索