01?概述
????索引是幫助MySQL高效獲取數據的排好序的數據結構,用于快速找出某個列中有一特定值的行。
????通過上述定義可以理解索引三個基本特性:
??? 1、索引的作用是為了追求高效查找;
????2、索引是一種數據結構,且是有序的;
????3、索引用于快速查找某一個特定值的行(非特定值情況,即模糊匹配的情況是無效的)。
????不使用索引,MySQL必須從第一條記錄開始讀完整個表,直到找到相關的行,表越大,查詢數據所花費的時間就越多(全表掃描)。如果表中查詢的列有一個索引,MySQL能夠快速到達一個表搜索數據文件,而不必查看所有數據,那么將會節省很大一部分時間。
1.1 隱式索引
隱式索引由數據庫服務器在創建某些對象的時候自動生成。例如,對于主鍵約束和唯一約束,數據庫服務器就會自動創建索引。
????之所以會存在隱式索引,是因為數據庫總是優先考慮使用索引的這種情況,除非索引失效,但是前提是需要有字段建立索引,如果用戶不手動顯式指定索引字段,那么數據庫就自動添加一個。一切都是為了盡可能的將能優化的情況都考慮到。
1.2 特點
? ??優點:
????1、提高數據檢索效率,降低數據庫的IO成本;
????2、通過索引對數據進行排序,降低數據排序的成本,降低了CPU的消耗。
? ??缺點:
????1、實際上索引也是一張表,該表保存了主鍵與索引字段,并指向實體表的記錄,所以索引也是要占空間的(空間換時間);
????2、雖然索引大大提高了查詢速度,同時會降低更新表的速度,如對表進行INSERT、UPDATE、DELETE操作。
1.3 應用場景
? ??需要創建索引:
????1、主鍵自動創建唯一索引
????2、頻繁作為where查詢條件的字段應該創建索引
????3、查詢中與其他表關聯的字段,外鍵關系建立索引
????4、查詢中排序的字段(order by),排序的字段若通過索引區訪問將大大提高排序速度
????5、查詢中統計或者分組字段(group?by)
????注:order?by/group by是先做排序再分組,因此可以建索引
???不需要創建索引:
????1、頻繁更新的字段不適合建立索引,因為每次更新不單單是更新了記錄還會更新索引
????2、WHERE條件里用不到的字段不創建索引
????3、表記錄太少,即小的數據表不應當使用索引
????4、經常增刪改的表
????5、如果某個數據列包含許多重復的內容,為它建立索引就沒有太大的實際效果
????6、數據值分布比較均勻的不適合建索引,比如性別
????7、如果列中包含大數或者NULL值,不宜創建索引
02?分類
2.1 根據索引字段
? ??1、單值索引
????單值索引即一個索引只包含單個列,一個表可以有多個單列索引。
? ??2、復合/聯合索引
????一個索引包含多個列,例如:INDEX Multidx(id,name.age)。
????創建單列索引還是復合索引,要看每次查詢中,哪些列在作為過濾條件的WHERE子句中最常出現。
????如果只需要一列,那么就應當創建單列索引。如果作為過濾條件的WHERE子句用到了兩個或者更多的列,那么復合索引就是最好的選擇。
2.2 根據索引字段類型
????1、主鍵索引/聚簇索引
????聚簇索引并不是一種單獨的索引類型,而是一種數據存儲方式。聚簇索引總是把數據行存儲在葉子頁中(即數據和索引都存儲在葉子節點),因此一個表中只能有一個聚簇索引。
????對于上述聚簇索引結構,主鍵a為索引,則葉子節點就是最終的數據存儲節點,查找到葉子節點也就對應找到了對應的數據了。
????并不是所有的存儲引擎都支持聚簇索引。
??? InnoDB只有一個聚集索引:
????默認會拿主鍵id作為聚集索引;
????如果沒有主鍵,會取非空的唯一索引作為聚集索引;
????如果沒有主鍵和非空唯一索引,則InnoDB會自己維護一個唯一row_id作為聚集索引。
????聚簇索引的優點如下:
????可以把相關數據保存在一起;
????數據訪問更快;
????使用覆蓋索引掃描的查詢可以直接使用頁節點中的主鍵值。
????當然,聚簇索引也有它的缺點:
????聚簇索引最大限度提高了I/O密集型應用的性能,但如果所有的數據都存放在內存中,聚簇索引就沒有優勢了;
????插入速度嚴重依賴插入順序,這也是為什么InnoDB一般都會設置一個自增的int列作為主鍵;
????更新聚簇索引的代價很高,因為會強制InnoDB將每個被更新的行移到新的位置;
????如果不按順序插入新數據時,可能會導致“頁分裂”;
????二級索引訪問可能會需要進行回表查詢。
????2、唯一索引
????索引列的值必須唯一,但允許有空值。
??? UNIQUE就是唯一索引,即要求每一行的數據必須唯一,但可以為空。
? ? 3、普通索引/二級索引/輔助索引
????InnoDB 中的輔助索引在葉子節點中并不存儲實際的數據,只會包含主索引的值(即索引和數據分開存儲)。這就意味著如果使用輔助索引進行數據的查找,只能查到主索引,然后根據這個主索引再次掃描以下主索引的樹,進行一次回表操作。
????對于上述非聚簇索引,假設字段a、b、c為索引,a為主鍵,則最終查找行記錄的時候,需要通過主鍵a在葉子節點的位置,獲取具體數據的位置信息,然后再去具體地址查找行記錄。即獲取葉子節點的同時并不能直接獲取最終結果,需要經過二次查找。
????這種索引也可以叫二級索引或者輔助索引。
????注:MyISAM是非聚集索引(對應文件MYI和MYD,分別存儲索引和數據信息),InnoDB是聚集索引(對應文件ibd,數據和索引信息)。
????4、全文索引
????全文索引只有在MyISAM索引上才能使用,只能在CHAR、VARCHAR、TEXT類型字段上使用全文索引。
????5、空間索引
????空間索引是對空間數據類型的字段建立的索引。
03?原理
3.1?頁
??? Mysql的基本存儲結構是頁(記錄都存在頁里邊),數據頁的特點:
????1、各個數據頁可以組成一個雙向鏈表;
????2、而每個數據頁中的記錄又可以組成一個單向鏈表;
??? 1)每個數據頁都會為存儲在它里邊兒的記錄生成一個頁目錄,在通過主鍵查找某條記錄的時候可以在頁目錄中使用二分法快速定位到對應的槽,然后再遍歷該槽對應分組中的記錄即可快速找到指定的記錄;
??? 2)以其他列(非主鍵)作為搜索條件:只能從最小記錄開始依次遍歷單鏈表中的每條記錄。
????所以,select * from user where username='xxxx',默認會這樣做:
??? 1、定位到記錄所在的頁:需要遍歷雙向鏈表,找到所在的頁;
??? 2、從所在的頁中查找相應的記錄。
3.2?二叉樹
????如果用二叉樹建立索引的數據結構,那么每個節點存儲K-V,即索引字段和索引字段所在行的磁盤地址的指針,時間復雜度為O(logN)。
????但是,實際應用中數據庫不使用二叉樹作為索引的數據結構,主要是因為二叉樹會出現極度不平衡的情況:
3.3?紅黑樹
????為了應對極端不平衡的情況,嘗試使用紅黑樹作為索引的數據結構。紅黑樹是二叉平衡樹。
????紅黑樹不適合作為索引的特殊場景:存儲數據非常大的時候,樹的高度h太大,查找的數據如果在葉子節點,極端的情況下需要查詢高度h次才可以。
????我們需要控制查找次數,即樹的高度,而紅黑樹每個節點只存儲一個K,則為了降低高度,可以在一個節點存儲多個數據,從而可以將樹的高度降低,而B+樹能夠控制樹的高度在3~5。
3.4?B Tree
????B類樹的一個很鮮明的特點就是樹的層數比較少,而每層的節點都非常多,樹的每個葉子節點到根節點的距離都是相同的(這也是為什么叫Balance Tree的原因)。
????另外,樹的每一個節點都是一個數據頁(B+樹是葉子結點是數據頁,其余全部為索引頁),這樣每個節點只需要一次IO就可以全部讀取。這樣的結構保證了查詢數據時能盡量少地進行磁盤IO,同時保證IO的穩定性。
? ??特點:
??? 1、葉子節點具有相同的深度,葉子節點的指針為空;
??? 2、所有索引元素不重復;
??? 3、節點中的數據索引從左到右遞增排列。
????注:為了能夠存儲足夠多的數據的同時控制樹的高度,一個節點存儲數據的個數由表數據和高度確定。如果數據較多,則每個節點存儲數據多一些,這樣高度就降下來了。
????模擬查找關鍵字29的過程:
??? 1、根據根節點找到磁盤塊1,讀入內存。【磁盤I/O操作第1次】
??? 2、比較關鍵字29在區間(17,35),找到磁盤塊1的指針P2。
??? 3、根據P2指針找到磁盤塊3,讀入內存。【磁盤I/O操作第2次】
??? 4、比較關鍵字29在區間(26,30),找到磁盤塊3的指針P2。
??? 5、根據P2指針找到磁盤塊8,讀入內存。【磁盤I/O操作第3次】
??? 6、在磁盤塊8中的關鍵字列表中找到關鍵字29。
????分析上面過程,發現需要3次磁盤I/O操作,和3次內存。
????但如果我們想進行范圍查找,查詢10~79之間的數據,就需要從跟節點一個一個往下查,范圍跨度越大,則磁盤IO的次數就越多,性能越差。?
????由于B樹的節點除了存儲Key還存儲Data,這樣每一層存儲的Key有限,也就是說這樣導致層高不可控,磁盤IO較多。如果除了葉子節點都存儲Key值,這樣就可以很快找到葉子節點,進而找到Data,這就引入B+樹。
????應用:MongoDB使用B樹。
??? MongoDB是一個通用的、面向文檔的分布式數據庫,區別于傳統的關系型數據庫 MySQL、Oracle 和SQL Server,MongoDB最重要的一個特點就是面向文檔:
??? 1、作為非關系型的數據庫,MongoDB對于遍歷數據的需求沒有關系型數據庫那么強,它追求的是讀寫單個記錄的性能;
??? 2、大多數OLTP的數據庫面對的都是讀多寫少的場景,B 樹與 LSM 樹在該場景下有更大的優勢。
????上述的兩個場景都是MongoDB需要面對和解決的,所以我們會在這兩個常見場景下對不同的數據結構進行比較。
3.5 B+ Tree
??? B+ Tree和 B Tree不同,B+ Tree中,只能將數據存儲在葉子結點中,內部節點將只包含指針,而B Tree可以將數據存儲在內部的葉節點中。
????因此B+ Tree的關鍵優勢是中間節點不包含數據,因此B+ Tree的大小遠小于B Tree,并且可以將更多數據存儲到存儲器中。另外,B+ Tree的每一個葉子節點包含了到相鄰的節點的鏈接,這樣可以快速地進行范圍遍歷。
??? B+樹結構如下:
??? MySQL真正使用的數據結構不是B樹,而是B樹的變種B+樹,B+樹是一個平衡二叉樹,從根節點到每個葉子節點的高度差值不超過1,而且同層級的節點間有指針相互鏈接。其特點:
??? 1、非葉子節點不存儲data,只存儲索引(冗余),可以放更多的索引;
??? 2、葉子節點包含所有索引字段(非葉子節點也有一個相同的值,存在冗余,B樹則沒有冗余);
??? 3、葉子節點用指針連接,提高區間訪問的性能(B樹葉子節點是沒有這個指針的)。
????注:在非葉子節點不存儲data,因為InnoDB對于頁大小有限制(16KB),所以只存儲索引可以在每一行存儲更多的索引信息(B樹非葉子節點存儲data信息,浪費內存),將data放到葉子節點。例如,bigint的索引,則15占用8Byte,后面地址信息6Byte,則一個索引占8+6=14Byte,頁大小16KB,則大概可以放16KB/14Byte=1170個節點。如果葉子節點data占據1KB,則頁大小16KB的時候葉子節點可以存儲16個索引,則最終可以存儲的索引為1170*1170*16(假設是3層),這是千萬級別的數據。
????根節點一般事先加載到內存,所以在根節點可以快速定位加載的頁,從磁盤加載對應的數據頁到內存,實現快速查找。
????在B+樹葉子節點保存了所有的索引數據(不是所有的表數據),所有非葉子結點都會在葉子節點保留一份備份,所以B+樹節點樹比實際存儲的數據的個數要多。
? ??B樹與B+樹:
????1、B樹非葉子節點存儲data(索引數據的地址),B+樹非葉子節點只存儲索引信息;
????2、B樹不存在數據冗余,B+樹存在冗余(普通索引會存儲主鍵,主鍵索引會存儲所有數據,這樣就存在數據冗余了),葉子節點存儲所有數據。
3.6?Hash
????哈希索引就是采用一定的哈希算法,把鍵值換算成新的哈希值,檢索時不需要類似B+樹那樣從根節點到葉子節點逐級查找,只需一次哈希算法即可立刻定位到相應的位置,速度非常快。
????Hash索引結構的特殊性,其檢索效率非常高,索引的檢查可以一次定位,不像B-Tree索引需要從根節點到枝節點,最后才能訪問到葉節點。這樣多次的IO訪問,所以哈希索引的效率要高于B-Tree索引。
? ??哈希索引局限性:
??? 1、hash函數計算后的結果是隨機的,如果是磁盤上放置數據,比如主鍵id,那么隨著id的增長,id對應的行在磁盤上隨機放置,而隨機查詢非常慢;
??? 2、哈希索引也沒辦法利用索引完成排序;
????3、無法對范圍查詢進行優化;
????4、在有大量重復鍵值情況下,哈希索引的效率也是極低的(哈希碰撞問題);
????5、無法利用前綴索引,比如在B-Tree中,filed列的值“helloword”并添加索引,查詢xx=helloword時自然可以利用索引,xx=hello也可以利用索引(左前綴索引),因為hash(“helloword”和hash(“hello”)兩者的關系仍為隨機;
????6、排序無法優化;
????7、必須回行,就是說,通過索引拿到的數據位置,必須回到表中取數據。
3.7?LSM
????LSM Tree技術出現的一個最主要的原因就是磁盤的隨機寫速度要遠遠低于順序寫的速度,而數據庫要面臨很多寫密集型的場景,所以很多數據庫產品就把LSM Tree的思想引入到了數據庫領域。
??? LSM Tree顧名思義,就是The Log-Structured Merge-Tree的縮寫。從這個名稱里面可以看到幾個關鍵的信息:
????1、log-structred,通過日志的方式來組織的
????2、Merge,可以合并的
????3、Tree,一種樹形結構
????實際上它并不是一棵樹,也不是一種具體的數據結構,它實際上是一種數據保存和更新的思想。簡單地說,就是將數據按照key來進行排序(在數據庫中就是表的主鍵),之后形成一顆一顆的樹形結構,或者不是樹形結構,是一張小表也可以,這些數據通常被稱為基線數據;之后把每次數據的改變(也就是log)都記錄下來,也按照主鍵進行排序,之后定期的把log中對數據的改變合并(merge)到基線數據當中。
????注:OceanBase采用LSM。
04?基本操作?
4.1 創建索引
????CREATE INDEX 索引名稱 ON table?(column1,…);?
4.2 刪除索引
????DROP INDEX 索引名稱 ON 表名;
4.3 查看索引
????SHOW INDEX FROM 表名;
05?索引失效
5.1 最左前綴原則/ABC問題
????即便表建立了索引,where查詢語句中存在索引,也不一定走索引,是不是走索引遵循最左前綴原則:
????索引對應的數據結構B+樹生成的時候就是按照最左的字段排序生成的,如果不指明該字段的取值,那么就無法確定走索引B+樹的左還是右,進而只能全表掃描。
5.2?索引失效的情況
????1、列類型是字符串,查詢條件未加引號
????2、未使用該列作為查詢條件
????3、使用like時通配符在前
????4、在查詢條件中使用OR
????5、索引字段做計算(格式轉換,運算)
????6、字符類型索引=數值類型字段值
????7、不符合最左前綴/聯合索引ABC問題
07?索引優化
7.1 設計高效索引策略,避免索引失效
? ? 設計高效索引策略的時候需要考慮以下的情況:
????1、索引長度以及區分度;
??? 2、聯合索引需要考慮索引先后順序。
7.2 考慮索引覆蓋
????索引覆蓋是指,如果查詢的列恰好是索引的一部分,那么查詢只需要在索引文件上進行,不需要回行到磁盤再找數據(即避免回表操作),這種查詢速度非常快,稱為“索引覆蓋”。
????覆蓋索引是一個非常有用的工具,可以極大提升性能。試想一下,如果一個查詢只需要掃描索引而無需二次回表查詢,會帶來什么好處:
??? 1、索引行通常遠小于數據行的大小,所以如果只需要索引,那么MySQL就會極大地減少數據訪問量;
??? 2、因為索引是按照順序存儲的,所以對于I/O密集型的范圍查詢會比隨機從磁盤讀取每一行數據的I/O要少的多;
????3、由于InnoDB的聚簇索引,所以覆蓋索引對InnoDB特別有用。
7.3 索引下推
??? Index Condition Pushdown(索引下推),MySQL 5.6引入了索引下推優化,是一種在存儲引擎層使用索引過濾數據的一種優化方式,ICP可以減少存儲引擎訪問基表的次數以及MySQL服務器訪問存儲引擎的次數。
????默認開啟,使用SET optimizer_switch = 'index_condition_pushdown=off';可以將其關閉。
????官方文檔中給的例子和解釋如下:
????people表中(zipcode,lastname,firstname)構成一個索引
????SELECT * FROM people WHERE zipcode='95054' AND lastname LIKE '%etrunia%' AND address LIKE '%Main Street%';
????如果沒有使用索引下推技術,則MySQL會通過zipcode='95054'從存儲引擎中查詢對應的數據,返回到MySQL服務端,然后MySQL服務端基于lastname LIKE '%etrunia%'和address LIKE '%Main Street%'來判斷數據是否符合條件。
????如果使用了索引下推技術,則MySQL首先會返回符合zipcode='95054'的索引,然后根據lastname LIKE '%etrunia%'和address LIKE '%Main Street%'來判斷索引是否符合條件。如果符合條件,則根據該索引來定位對應的數據,如果不符合,則直接reject掉。有了索引下推優化,可以在有like條件查詢的情況下,減少回表次數。