MySQL之索引分享

索引本質

官方對索引的定義爲:索引(Index)是幫助MySQL高效獲取數據的數據結構
本質:索引是數據結構

Innodb B-Tree 索引

  1. B+樹成因:
    我們知道,每一種數據結構的出現都是爲了解決特定的問題,那麼B+樹的出現是爲了解決什麼問題,那就是:每次查找數據時把磁盤IO次數控制在一個很小的數量級,最好是常數數量級。在理解B+樹結構之前,先提一下計算機的局部性原理:當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。每一次IO讀取的數據我們稱之爲一頁(page)。具體一頁有多大數據跟操作系統有關,一般爲4k或8k,也就是我們讀取一頁內的數據時候,實際上才發生了一次IO,這個理論對於索引的數據結構設計非常有幫助。
  2. B+樹結構
    B+樹的定義可以參見B+樹,我們先看一下B+樹的結構:
    #pic_center
    淺藍色的塊我們稱之爲一個磁盤塊,可以看到每個磁盤塊包含幾個數據項(深藍色所示)和指針(黃色所示),如磁盤塊1包含數據項17和35,包含指針P1、P2、P3,P1表示小於17的磁盤塊,P2表示在17和35之間的磁盤塊,P3表示大於35的磁盤塊。真實的數據存在於葉子節點即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非葉子節點只不存儲真實的數據,只存儲指引搜索方向的數據項,如17、35並不真實存在於數據表中。
  3. B+樹的查找過程
    如圖所示,如果要查找數據項29,那麼首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO,在內存中用二分查找確定29在17和35之間,鎖定磁盤塊1的P2指針,內存時間因爲非常短(相比磁盤的IO)可以忽略不計,通過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內存,發生第二次IO,29在26和30之間,鎖定磁盤塊3的P2指針,通過指針加載磁盤塊8到內存,發生第三次IO,同時內存中做二分查找找到29,結束查詢,總計三次IO。真實的情況是,3層的b+樹可以表示上百萬的數據,如果上百萬的數據查找只需要三次IO,性能提高將是巨大的,如果沒有索引,每個數據項都要發生一次IO,那麼總共需要百萬次的IO,顯然成本非常非常高。
  4. B+樹性質
    1.通過上面的分析,我們知道IO次數取決於b+數的高度h,假設當前數據表的數據爲N,每個磁盤塊的數據項的數量是m,則有h=㏒(m+1)N,當數據量N一定的情況下,m越大,h越小;而m = 磁盤塊的大小 / 數據項的大小,磁盤塊的大小也就是一個數據頁的大小,是固定的,如果數據項佔的空間越小,數據項的數量越多,樹的高度越低。這就是爲什麼每個數據項,即索引字段要儘量的小,比如int佔4字節,要比bigint8字節少一半。這也是爲什麼b+樹要求把真實的數據放到葉子節點而不是內層節點,一旦放到內層節點,磁盤塊的數據項會大幅度下降,導致樹增高。當數據項等於1時將會退化成線性表。

疑問:我們都知道一個表不能存儲太多數據,否則會造成查詢性能下降,那麼個MySQL的一個表大概存儲多少行記錄會導致性能下降?
這個問題的簡單回答是:約2千萬
那如何計算呢?首先我假設每行記錄爲1k,
首先假設樹高爲2,那麼根節點存儲的指針對應每個葉子節點。也就是說,最大的話,有多少個指針就有多少個葉子節點。
這棵B+樹存儲的總行數=根節點指針樹*每個葉子節點的行記錄樹。
那麼根節點能存儲多少指針呢?
InnoDB最小單位爲頁,一頁爲16k,我們假設主鍵ID爲bigint類型,長度爲8字節,而指針大小在InnoDB源碼中設置爲6字節,這樣一共14字節,那麼一頁的總指針樹爲16k/(8+6) = 1170。
每個指針對應1頁,假設每行記錄爲1k(實際上現在很多互聯網業務數據記錄大小通常就是1K左右),那麼一頁大概能存16條行記錄。
所以這棵B+樹總行數爲:1170 * 16 = 18720
三層的話,總指針數爲1170 * 1170,故總行數爲1170 * 1170 * 16=21902400
所以當單表數據超過千萬級別後,就得考慮分表了,否則B+樹的層級可能會超過3級,造成查詢效率下降。

高性能索引策略

主鍵索引

  1. 介紹
    InnoDB中,表數據文件本身就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵。
  2. 主鍵索引結構
    在這裏插入圖片描述
    從上圖可以看到葉節點包含了完整的數據記錄,這種索引叫也做聚集索引。

輔助索引

  1. 介紹
    InnoDB的輔助索引與主鍵索引結構類似,不過data域存儲相應記錄主鍵的值而不是地址。
  2. 輔助索引結構
    在這裏插入圖片描述
    這裏以英文字符的ASCII碼作爲比較準則。聚集索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然後用主鍵到主索引中檢索獲得記錄。

聯合索引

  1. 介紹
    聯合索引是指對錶上的多個列合起來做一個索引。
  2. 結構
    假定兩個鍵值得名稱分別爲a、b如圖:
    在這裏插入圖片描述
    可以看到這與我們之前看到的單個鍵的B+樹並沒有什麼不同,鍵值都是排序的,通過葉子結點可以邏輯上順序地讀出所有數據,就上面的例子來說,即(1,1),(1,2),(2,1),(2,4),(3,1),(3,2),數據按(a,b)的順序進行了存放。
    因此,對於查詢select * from table where a=xxx and b=xxx, 顯然是可以使用(a,b) 這個聯合索引的,對於單個列a的查詢select * from table where a=xxx,也是可以使用(a,b)這個索引的。
    但對於b列的查詢select * from table where b=xxx,則不可以使用(a,b) 索引,其實你不難發現原因,葉子節點上b的值爲1、2、1、4、1、2顯然不是排序的,因此對於b列的查詢使用不到(a,b) 索引。
    聯合索引的第二個好處是在第一個鍵相同的情況下,已經對第二個鍵進行了排序處理,例如在很多情況下應用程序都需要查詢某個用戶的購物情況,並按照時間進行排序,最後取出最近三次的購買記錄,這時使用聯合索引可以幫我們避免多一次的排序操作,因爲索引本身在葉子節點已經排序了

覆蓋索引

覆蓋索引,即從輔助索引中就可以得到查詢所需要的所有字段值,而不需要查詢聚集索引中的記錄。覆蓋索引的好處是輔助索引不包含整行記錄的所有信息,故其大小要遠小於聚集索引,因此可以減少大量的IO操作。

前綴索引

前綴索引:在對一個比較長的字符串進行索引時,可以僅索引開始的一部分字符,這樣可以大大的節約索引空間,從而提高索引效率.但是這樣也會降低索引的選擇性.
索引的選擇性: 不重複的值/所有的值. 可以看出索引的選擇性爲0-1,最高的就是該列唯一,沒有重複值.所以唯一索引的效率是比較好的.
計算索引選擇性:

select 
    count(distinct left(school_name,3))/count(*) as sch3, 
    count(distinct left(school_name,4))/count(*) as sch4,
    count(distinct left(school_name,5))/count(*) as sch5,
    count(distinct school_name)/count(*) as original
from 
    user;

其中查找到的original就是原本的選擇性,sch3,sch4,sch5分別是取該列的前3,4,5個字符作爲索引的時候的選擇性.逐步增加這個數值,當選擇性與原來相差不大的時候,就是一個比較合適的前綴索引的長度

實戰

在做項目的時候,我遇到過一個用索引優化的例子是這個:

根據某號碼後6位搜索數據。如果我們使用比如:

select * from t1 where fin like ‘%123456’;
或者select * from t1 where fin like ‘%123456%’;

這種是用不到索引的,因爲字符串中間有’101’的字符串並沒有排好序,所以只能全表掃描了。
優化的方式就是,在表裏再存一列底盤號的反轉數據,比如正常的是abc123456,那麼反轉的就是654321cba,後面查詢的時候就可以轉換爲

select * from t1 where fin like '654321%';

這樣就能用到索引了。

Optimizer Switch

mysql 5.1中開始引入optimizer_switch,在mysql優化語句過程中,可通過設置optimizer_switch控制優化行爲。使用select @@optimizer_switch;可以查詢當前MySQL支持的優化行爲。下面介紹兩個比較常用的優化行爲。

ICP(索引下推)

1. 介紹
索引條件下推優化(Index Condition Pushdown (ICP) )是MySQL5.6添加的,用於優化數據查詢。 它的作用針對基於輔助/第二索引的查詢,過濾不必要的回表查詢減少回表次數,提高查詢效率。

2. 原理
圖一:不使用ICP技術
要獲取數據,首先讀取索引信息,然後根據索引將整行數據讀取出來。
然後通過where條件判斷當前數據是否符合條件,符合返回數據。
在這裏插入圖片描述
圖二:使用ICP技術

  1. 獲取下一行的索引信息。
  2. 檢查索引中存儲的列信息是否符合索引條件,如果符合將整行數據讀取出來,如果不符合跳過讀取下一行。
  3. 用剩餘的判斷條件,判斷此行數據是否符合要求,符合要求返回數據。
    在這裏插入圖片描述

索引下推的條件必須是覆蓋索引,因爲它的原理是利用覆蓋索引中的數據來過濾條件,從而達到減少回表的次數。
比如建立覆蓋索引(name,age),查詢條件爲(陳%,20),如果不適用覆蓋索引,則是先將以陳開頭的人的主鍵索引先查詢出來(比如10個數據),然後根據這十個索引查詢數據,然後再過濾年齡爲20歲的。如果使用索引下推,則會在查詢回表之前,先將年齡不是20的索引給過濾掉(因爲覆蓋索引中有年齡的數據,所以可以用到),再用過濾後的主鍵索引去回表查詢,減少回表次數。

3.相關參數

索引下推優化是默認開啓的。可以通過下面的腳本控制開關SET optimizer_switch =
‘index_condition_pushdown=off’; SET optimizer_switch = ‘index_condition_pushdown=on’;

MRR(多範圍讀)

1.介紹
Multi-Range Read 多範圍讀(MRR) 也是MySQL 5.6版本提供了很多性能優化的特性之一。
它的作用針對基於輔助/第二索引的查詢,減少隨機IO,並且將隨機IO轉化爲順序IO,提高查詢效率。

2.原理
在沒有MRR之前,或者沒有開啓MRR特性時,MySQL 針對基於輔助索引的查詢策略:
第一步 先根據where條件中的輔助索引獲取輔助索引與主鍵的集合,結果集爲rest。
第二步 通過第一步獲取的主鍵來獲取對應的值。
在這裏插入圖片描述
由於MySQL存儲數據的方式: 輔助索引的存儲順序並非與主鍵的順序一致,從圖中可以看出,根據輔助索引獲取的主鍵來訪問表中的數據會導致隨機的IO . 不同主鍵不在同一個page 裏面時必然導致多次IO 和隨機讀。

在使用MRR優化特性的情況下,MySQL 針對基於輔助索引的查詢策略是這樣的:
第一步 先根據where條件中的輔助索引獲取輔助索引與主鍵的集合,結果集爲rest
第二步 將結果集rest放在buffer裏面(read_rnd_buffer_size 大小直到buffer滿了),然後對結果集rest按照pk_column排序,得到結果集是rest_sort
第三步 利用已經排序過的結果集,訪問表中的數據,此時是順序IO.
在這裏插入圖片描述
從圖示MRR原理,MySQL 將根據輔助索引獲取的結果集根據主鍵進行排序,將亂序化爲有序,可以用主鍵順序訪問基表,將隨機讀轉化爲順序讀,多頁數據記錄可一次性讀入或根據此次的主鍵範圍分次讀入,以減少IO操作,提高查詢效率。

3.相關參數:

我們可以通過參數 optimizer_switch的標記來控制是否使用MRR,當設置mrr=on時,表示啓用MRR優化。mrr_cost_based 表示是否通過 cost base的方式來啓用MRR.如果選擇mrr=on,mrr_cost_based=off,則表示總是開啓MRR優化。

後話:講這兩個優化器開關主要是擴展一下我們優化查詢的思路,除了在索引上動腦子外,瞭解一下MySQL的優化器也是一個不錯的優化方式吧。

補充:
B- 樹
在這裏插入圖片描述
B+樹:
它與 B- 樹的不同之處在於:

  • 所有關鍵字存儲在葉子節點出現,內部節點(非葉子節點並不存儲真正的 data)

  • 爲所有葉子結點增加了一個鏈指針
    在這裏插入圖片描述

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