MySQL索引的那些事兒

1、背景描述

索引是幫助MySQL高效查找的數據結構,他的本質是空間換時間。查找可以分爲兩種,第一種順序查找,mysql中最差的情況下就是全表掃描,一行一行數據找直到找到全部符合條件的項目,顯然在大量數據情況下查找數據是非常慢的,所以就有了另一種查找方式–索引查找,索引相當於書籍的目錄,圖書館的書籍ISBN號,一個國家中學生的學籍號,通過索引可以非常快的、精確地查找到需要的數據。
在mysql的優化中索引佔據半壁江山,索引設置的好壞直接影響相關sql語句的執行效率,一次查詢,可能執行時間50s,也可能倍數減少到幾秒,零點幾秒。

魯迅說:索引是門藝術

2、MySQL中的索引分類

索引按照類型條件劃分爲

  1. 主鍵索引
    主鍵索引,表中添加主鍵時自動創建,查找速度最快。
  2. 唯一索引
    唯一索引,即用unique修飾的索引,顧名思義,索引包含的字段唯一存在,不能有重複的值。
  3. 全文索引
    全文索引,即用fulltext修飾的索引,可以類比模糊查詢,在大量文字中查詢相關內容,支持的字段有varchar、char、text,以前只有myisam引擎支持全文索引,但是在MySQL5.6版本以後Innodb也開始支持全文索引。
  4. 普通索引
    好像沒啥可解釋的,普通嘛,沒有那麼多限制就對了,就是除了主鍵索引、唯一索引、全文索引等剩下的就是了。

按照包含列分爲

  1. 單列索引
    即只包含單個字段的索引。
  2. 聯(復)合索引(多列索引)
    即包含多個字段的索引。

按照數據結構:

  1. 聚簇索引(聚集索引)
    聚簇索引中葉子節點中包含記錄數據和索引,可以直接通過主鍵查詢到對應數據。
  2. 二級索引(輔助索引)
    二級索引中只包含主鍵值和對應索引列的值,每次查詢都需要先查詢到主鍵值,然後通過使用主鍵值再去聚簇索引中查詢對應數據內容,這個過程也叫作“回表”操作。
  3. 哈希索引
    在Innodb等引擎中,哈希索引是由mysql本身維護的,不可以手動使用,又名自適應哈希索引,Adapter Hash Index(AHI),計算索引列字段哈希值和記錄指針地址保存在內存中,不保存其他字段數據,以索引列字段值哈希值排序,適合等值精確查找。
    在但有個例外memory引擎中,用戶可以手動創建使用哈希索引。

其他:

  1. 覆蓋索引
    覆蓋索引即查詢時的字段是索引的子集,查詢時使用的字段一定在索引包含的字段中,這時候使用的索引就叫做覆蓋索引。explain執行計劃中Extra列值爲using index是使用覆蓋索引的顯著標志。

3、索引數據結構

mysql處理數據是在內存中的,存儲數據是在硬盤上,所以mysql在進行sql操作時,就需要數據在內存和磁盤中來回交互,即我們通常所說的IO操作。這種交互操作是以“數據頁”的方式來交換數據,每次交換的數據大小默認爲16K,也就是說內存每次從硬盤中拿到16K的數據信息進行操作,操作完成後每次刷新16K的數據到硬盤中。

說到索引的數據結構,最核心最重要的就是B+樹了,每一條索引就是一棵B+樹。
在InnoDB引擎中只有主鍵索引是聚簇索引,其他索引列和複合索引都是二級索引。
聚簇索引的結構如圖:
在這裏插入圖片描述在這裏插入圖片描述
由圖中可以看出,聚簇索引B+樹中根節點和非葉子節點只保存主鍵和頁號,而在葉子節點中除了主鍵和頁號外,還保存了完整的數據列內容,同時在一棵樹上同時保存了索引和數據,這也是“聚簇索引”這個名字的由來。主鍵是維繫聚簇索引的核心要素,B+樹中的所有元素都是依據主鍵而存在。同時數據頁之間通過主鍵的大小排序形成了雙向鏈表,數據記錄之間通過主鍵大小排序形成了單向鏈表。
由於聚簇索引的數據的物理存儲順序和索引順序一致,即只要索引相鄰,那麼對應的物理存儲也會存儲在磁盤相鄰的位置,這樣就能得出一個結論,主鍵靠的越近,聚簇索引數據越緊湊,產生的磁盤鎖片也會越少,效率也會更高。這也是爲什麼推薦主鍵自增的原因,在往磁盤中寫數據時就是一頁一頁挨着寫,避免出現“頁分裂”,自然效率也就相對於不斷調整數據順序調換位置更高。

二級索引的結構如圖
在這裏插入圖片描述

二級索引中保存了對應的索引列和主鍵值,當沒有使用覆蓋索引查詢時,二級索引中只能找到索引信息,想要完整數據記錄信息就需要在二級索引中找到對應的主鍵值,然後再回到聚簇索引中查詢相應的完整數據記錄。這就是前面提到的"回表”操作。

myisam中索引結構:
接下來說說myisam引擎中的數據結構,與Innodb相同的一點是,myisam中索引也同樣採用B+樹存儲,myisam的存儲有三個文件,除了frm文件以外有myi索引文件和myd數據文件,區別於innodb引擎,除了frm文件以外就只有一個ibd文件,myisam索引和數據本身就是獨立存儲的。myisam中的B+樹中根節點和非葉子節點存儲主鍵值,葉子節點存儲數據對應指針地址。
在這裏插入圖片描述
myisam中存在的都是二級索引,但與Innodb中的二級索引不同的是,myisam中的二級索引葉子節點存儲的是數據記錄指針地址,查詢數據時不再需要回表操作,而是直接通過指針地址查找對應的數據記錄,這也是myisam之所以說查詢速度快的很重要的原因。

4.爲什麼是B+樹

數據結構那麼多,光一個樹就有好多種,爲什麼單單就選擇了B+樹呢,爲什麼不是哈希表,爲什麼不是AVL平衡二叉樹,爲什麼不是B樹多叉平衡查找樹?總結一下原因:

  1. B+樹特點:非葉子節點中只存儲主鍵/索引列等,不存儲完整數據,完整數據都存放在葉子節點中,在內存寸土寸金的情況下,一個16KB大小的數據頁當然是存儲的索引越多越好,相對於AVL和B-樹,這一點就很明顯的把B+樹和其他的樹劃開了界限。同時B+樹中索引和存放在葉子節點中的數據具有天然的已排序特點,在範圍查找和數據排序方面具有得天獨厚的條件;再者B+樹中葉子節點中存儲了完整數據結構,更有利於提高全表掃描的速度。
  2. 哈希表只存儲索引列哈希值和行記錄指針,不存儲其他列數據,並且存在於內存中,同時哈希算法得出的哈希值繞不開的存在哈希碰撞,任何時候都需要全表掃描,解決哈希碰撞帶來的額外消耗顯然是得不償失的。並且對於數據排序,範圍查詢等不友好,缺點太多啦,pass!!
  3. AVL平衡二叉樹,數據越多,樹的深度/層高也會越來越高。相對B+樹每個節點有多個子節點帶來的矮胖,AVL每個節點最多隻有兩個子節點,伴隨着數據的增加,不可避免的樹的深度也會相應的快速增加,直到長成一根瘦高的"旗杆",在大量數據中查找數據時,會帶來過多的IO操作次數,影響效率,所以pass!
  4. B樹,相對於AVL,B樹具有和B+樹一樣的矮胖特徵,也沒有哈希錶帶來的哈希碰撞,但是第一點已經提到了,B樹非葉子節點中需要存儲數據區,一個16KB的數據頁中,存儲的索引肯定要少於B+樹,大量數據查詢時,需要更多的IO操作來獲取數據,當然相同層高的B+樹和B樹在查詢根節點和非葉子節點數據的時候,查詢特定數據的情況下,B樹IO次數一定是少於B+樹的,越靠近根節點的數據查詢越快,反之越慢,適合單條查詢。MongoDB使用的就是這種數據結構。

這裏再插一句嘴,MySQL設計者認爲範圍查詢優先於單條數據查詢,所以選擇B+樹這種數據結構契合需求,而MongoDB設計者認爲還是普通單條查詢使用場景更多,所以選擇了B樹這種數據結構,在查詢單條數據時可以直接在數據區中拿到完整數據,並且平均查詢速度一般優於B+樹。

5.索引帶來的好處和壞處

索引的好處前面都已經說了很多了,以空間換時間的方式,提高mysql獲取數據的速度和效率嘛,查詢更快,排序更快,挺好啊,所以更多的索引是不是更好,NO!單看索引帶來的好處確實是越多越好,但是索引是空間換時間,這樣就意味着更多的索引佔據更多的存儲空間,同時,索引建立後,mysql每次修改、更新、刪除數據的時候,都需要同時去維護相應的索引,會帶來大量的時間消耗,這顯然是和空間換時間的初衷相悖,這個時候索引當然是越少越好了,所以需要在多和少之間需要一個平衡,適合的纔是最好的嘛,至於怎麼平衡,往後看第8條- 如何選擇索引列~

6.爲什麼要手動創建主鍵

1.手動創建主鍵int佔4個字節,如果沒有手動創建,mysql也會貼心照顧你的懶惰或者粗心,會自動創建主鍵_rowid,佔用6個字節,你要不要偷這個懶?
2.Innodb自動創建的主鍵在使用行鎖時會自動升級成表鎖,併發能力降低。爲什麼?原諒我還沒有研究到,就當甲魚屁股-規定對待就好,要不要手動創建,你看着辦吧。

7.最左匹配原則

最左匹配原則是應用在聯合索引中,是聯合索引最重要的屬性,沒有之一。
雖然我們創建了索引,但是很多情況下,不合適的語句是不能使用索引來提高效率的,至於有哪些情況,我自己總結了個口訣,獻醜~

字段使用從左起,查詢順序沒關係,
索引字段要連續,範圍對比後無匹,
排序順序要一致,謹慎包含表達式,
前綴索引忌排序,左百分號不考慮

舉個栗子:建立聯合索引(name,age,sex)
字段使用從左起,意味着使用索引字段一定是從最左邊name開始的,類似於select name,age from table where age=10或者select name,age from table where sex=1這樣的查詢中索引是不生效的。
查詢順序沒關係,查詢時where條件中的索引字段順序是不影響索引生效與否的。例如select name,age from table where age=10 and name='tom’依舊會使用索引,mysql的查詢優化器會幫我們調整順序使用索引。
索引字段要連續,查詢時where條件中使用的索引字段一定是連續的,中間不能有丟失,例如select name from table where name=‘tom’ and sex=1中用到的索引字段只有name,而sex不會生效。
範圍對比後無匹,範圍對比之後的字段都會失效無法匹配索引,例如select name from table where name>‘tom’ and age=10只會使用到name字段,而不會使用age。
排序順序要一致,即索引字段在用於排序時,排序方式必須相同,不能是name desc降序,age asc升序,這時索引也會失效。
謹慎包含表達式,查詢字段必須是單獨存在,不能存在表達式中,例如select name from table where upper(name)='TOM’不能使用索引。
前綴索引忌排序,左百分號不考慮。使用列前綴添加索引一般是varchar,text等存儲大量文本內容內容的列,一般和like結合使用,官方推薦使用這種方式來降低索引佔用存儲空間,但是在使用列前綴添加索引時不能使用該字段索引排序,例如select name from table where name like ‘t%’ order by name,查詢時使用索引,但排序時不能使用索引排序。並且百分號出現在左邊的時候是不能使用索引的,例如select name from table where name like '%t’這種情況下是不能使用索引查找的。

8. 如何選擇索引列

條件一:列值離散程度:比如性別列,就男和女,數據庫裏可能就是0和1或者male和female(你要愣說太監、人妖、中性人,那我也無力反駁),當數據列數據只有幾種情況,或者幾乎一致的時候,創建了索引和全表掃描差別不大,還要佔用額外的空間,那爲什麼要創建索引呢,所以在可能需要創建索引的情況下,數據列值離散性越大越好。

條件二:在條件一的基礎上,where後面的列,order by後面的列,分組group的列一般要創建索引。

條件三:對於char,varchar,text這些類型存儲很多字節的數據列,在條件一和二的基礎上根據業務情況選擇特徵值明顯的列前綴建立索引,減小索引所佔空間嘛,在計算機裏精打細算總是沒錯的。
條件四 索引列的類型儘量小,能使用int就不使用bigint,索引數據越小,一個數據頁存儲的索引越多,減少IO消耗
條件五 主鍵自增,避免“頁分裂“ 和數據遷移。
條件六 儘量避免重複索引,一樣的效果,一般來說數據庫查詢時只會使用一條二級索引,重複了沒有意義,浪費存儲空間。當然什麼情況不一般,這個要說到 索引合併 的內容,比較複雜,需要的可以百度一下。

總結:適合的纔是最好的,索引列的選擇一定要根據實際業務情況來選擇,不能生搬硬套。

7.索引優化,執行計劃Explain

這部分內容較多,留着下一篇文章來寫。

8.覆蓋索引的典型場景

使用覆蓋索引在Innodb引擎中的最重要優點就是不需要回表。覆蓋索引查詢時的字段是索引的子集,即需要查詢的字段在索引中都已經有了,這時候查詢時就不需要回表操作了。

這裏說到覆蓋索引的典型場景–大數據量下分頁查詢
例如這樣的sql語句,

select * from table where age>10 limit 1000000,10;

分頁越往後,查詢時間越久,就可以使用覆蓋索引來優化,將sql改寫爲

select * from table t1 join (select id from table where age >10 limit 100000,10)t2 on t1.id=t2.id

或者

select * from table where id>=(select id from table where age>10 limit 100000,1) limit 10

就可以有效提高查詢效率,降低查詢時間。

9.唯一索引和普通索引的區別

說到唯一索引和普通索引的差別,就要分析一下使用這兩種索引在增刪改查時都發生了什麼。
查找時:由於唯一索引限制,符合條件的只會有一條數據,即查找時找到一次即返回,而普通索引需要 接着查找所有符合條件的數據,感覺查詢同樣的數據時可能唯一索引更快一些,但是有索引的存在,兩者之間的差別微乎其微,可以說忽略不計。
插入時:普通索引直接插入就好,唯一索引由於unique修飾,只能存在一條列值爲插入的值的記錄,所以在插入之前就需要判斷是不是已經有符合條件的數據,如果沒有就插入,反之報錯,所以唯一索引插入時等於進行了一次查找+一次插入,顯而易見,插入時唯一索引會更慢一些。
刪除時:與查找相同,刪除時唯一索引找到一次刪除即返回。普通索引需要接着查找符合條件的數據並刪除,感覺刪除同樣的數據可能唯一索引更快一些,但是有索引的存在,兩者之間的差別微乎其微,可以說忽略不計。
修改時:與查找相同,修改時唯一索引找到一次修改即返回。普通索引需要接着查找符合條件的數據並修改,感覺修改同樣的數據可能唯一索引更快一些,但是有索引的存在,兩者之間的差別微乎其微,可以說忽略不計。
綜上所述:在寫操作多的情況下不建議選擇使用唯一索引。

10.索引的增刪查SQL語句

創建表時即創建索引

create  table t1 (id int,
                        name char(5),
                        sex char(5),
                        unique key uni_name(name),
                        primary key(id),
                        index idx_sex(sex)                        
);

創建索引

CREATE [UNIQUE/FULLTEXT] INDEX `index_name` ON `table_name` (column_list);
// 或者
ALTER TABLE `table_name` ADD INDEX|UNIQUE|PRIMARY KEY|FULLTEXT  `index_name` (column list);

刪除索引

DROP index `index_name` ON `table_name` (column list);
// 或者
ALTER TABLE `table_name` DROP INDEX|UNIQUE|PRIMARY KEY `index_name` (column list);

查看索引

SHOW INDEX FROM|IN `table_name`;

修改索引:刪除後重新創建

11.寫在最後

文中內容都是我自己的理解,如果有錯誤或者不足請一定評論告訴我,萬分感謝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章