1. 程式人生 > >mysql索引原理以及優化

mysql索引原理以及優化

數據查找 操作系統 評價 b數 order by 使用場景 排序樹 如果 相關

一、常見查找算法:

  1.順序查找:

  • 最基礎的查找方法,對比每一個元素進行查找。在數據量很大的時候效率相當的慢。
  • 數據結構:有序或者無需的隊列
  • 時間復雜度:O(n)

  2.二分查找:

  • 二分查找首先要求數組有序
  • 每次查找從中間開始查找,比較查找對象和中間值,如果比中間值小,則去頭到中間值的範圍繼續二分查找。如果比中間值大,則去中間值以後的數組進行二分查找。
  • 如果某一步驟數組為空,則表示沒有此對象
  • 數據結構:有序數組
  • 時間復雜度:O(logN)

  3.二叉樹排序:

    二叉排序樹的特點是:

      a.若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;

      b.若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;

      c.它的左、右子樹也分別為二叉排序樹。

    搜索原理:

      a.若b是空樹,則搜索失敗,否則:

      b.若x等於b的根節點的數據域之值,則查找成功;否則:

      c.若x小於b的根節點的數據域之值,則搜索左子樹;否則:

      d.查找右子樹。

    數據結構:二叉排序樹
    時間復雜度: O(log2N)

  4.哈希散列法(哈希表)

    原理:先根據key值和哈希函數創建一個哈希表(散列表),然後根據鍵值,通過散列函數,定位數據元素位置。

    數據結構:哈希表
    時間復雜度:幾乎是O(1),取決於產生沖突的多少。

  5.分塊查找

    分塊查找又稱索引順序查找,它是順序查找的一種改進方法。其算法思想是將n個數據元素”按塊有序”劃分為m塊(m ≤ n)。每一塊中的結點不必有序,但塊與塊之間必須”按塊有序”;即第1塊中任一元素的關鍵字都必須小於第2塊中任一元素的關鍵字;而第2塊中任一元素又都必須小於第3塊中的任一元素,依次類推。

    算法流程:

      先選取各塊中的最大關鍵字構成一個索引表;

      查找分兩個部分:先對索引表進行二分查找或順序查找,以確定待查記錄在哪一塊中;然後,在已確定的塊中用順序法進行查找。

    這種搜索算法每一次比較都使搜索範圍縮小一半。它們的查詢速度就有了很大的提升,復雜度為。如果稍微分析一下會發現,每種查找算法都只能應用於特定的數據結構之上,例如二分查找要求被檢索數據有序,而二叉樹查找只能應用於二叉查找樹上,但是數據本身的組織結構不可能完全滿足各種數據結構(例如,理論上不可能同時將兩列都按順序進行組織),所以,在數據之外,數據庫系統還維護著滿足特定查找算法的數據結構,這些數據結構以某種方式引用(指向)數據,這樣就可以在這些數據結構上實現高級查找算法。這種數據結構,就是索引

二、平衡多路搜索樹B樹(B-tree)

三、索引數據結構設相關的計算機原理

  1. 計算機的主存(RAM)和外部存儲(硬盤,CD,SSD)

    a.主存的讀取速度快,相對於主存,外部磁盤的數據讀取速率要比主從慢好幾個數量級

    b.計算機主存一般比較小,實際數據庫中數據都是存儲到外部存儲器

  2.索引與主存和外部存儲的關系

    a.一般來說,索引本身也很大,不可能全部存儲在內存中,因此索引往往以索引文件的形式存儲的磁盤上

    b.索引查找過程中就要產生磁盤I/O消耗,相對於內存存取,I/O存取的消耗要高幾個數量級,所以評價一個數據結構作為索引的優劣最重要的指標就是在查找過程中磁盤I/O操作次數的漸進復雜度。

    c.索引的結構組織要盡量減少查找過程中磁盤I/O的存取次數

  3.主存存儲原理:

    目前計算機使用的主存基本都是隨機讀寫存儲器(RAM),現代RAM的結構和存取原理比較復雜,這裏本文拋卻具體差別,抽象出一個十分簡單的存取模型來說明RAM的工作原理。

    技術分享圖片

    1.從抽象角度看,主存是一系列的存儲單元組成的矩陣,每個存儲單元存儲固定大小的數據。

    2.每個存儲單元有唯一的地址,現代主存的編址規則比較復雜,這裏將其簡化成一個二維地址。

    3.通過一個行地址和一個列地址可以唯一定位到一個存儲單元。

    主存的存取過程:

      a.當系統需要讀取主存時,則將地址信號放到地址總線上傳給主存,主存讀到地址信號後,解析信號並定位到指定存儲單元,然後將此存儲單元數據放到數據總線上,供其它部件讀取

      b.寫主存的過程類似,系統將要寫入單元地址和數據分別放在地址總線和數據總線上,主存讀取兩個總線的內容,做相應的寫操作。

      c.主存存取的時間僅與存取次數呈線性關系,因為不存在機械操作,兩次存取的數據的“距離”不會對時間有任何影響

      d.例如,先取A0再取A1和先取A0再取D3的時間消耗是一樣的。

  4.磁盤存儲原理

    1.索引一般以文件形式存儲在磁盤上,索引檢索需要磁盤I/O操作。

    2.與主存不同,磁盤I/O存在機械運動耗費,因此磁盤I/O的時間消耗是巨大的。

    3.磁盤讀取數據靠的是機械運動,當需要從磁盤讀取數據時,系統會將數據邏輯地址傳給磁盤,磁盤的控制電路按照尋址邏輯將邏輯地址翻譯成物理地址,即確定要讀的數據在哪個磁道,哪個扇區。

    4.為了讀取這個扇區的數據,需要將磁頭放到這個扇區上方,為了實現這一點,磁頭需要移動對準相應磁道,這個過程叫做尋道,所耗費時間叫做尋道時間。

    5.然後磁盤旋轉將目標扇區旋轉到磁頭下,這個過程耗費的時間叫做旋轉時間。

    6.最後便是對讀取數據的傳輸

    7.每次讀取數據花費的時間可以分為尋道時間、旋轉延遲、傳輸時間三個部分。

    (ps.各個時間的花費:)

      • 尋道時間是磁臂移動到指定磁道所需要的時間,主流磁盤一般在5ms以下。
      • 旋轉延遲就是我們經常聽說的磁盤轉速,比如一個磁盤7200轉,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2 = 4.17ms。
      • 傳輸時間指的是從磁盤讀出或將數據寫入磁盤的時間,一般在零點幾毫秒,相對於前兩個時間可以忽略不計。

    I/O操作問題:

      a. 訪問一次磁盤的時間,即一次磁盤IO的時間約等於5+4.17 = 9ms左右.

      b.一臺500 -MIPS的機器每秒可以執行5億條指令.

      c.因為指令依靠的是電的性質,換句話說執行一次IO的時間可以執行40萬條指令.

      d.數據庫動輒十萬百萬乃至千萬級數據,每次9毫秒的時間,顯然是個災難。

  5、磁盤預讀

    因此為了提高效率,要盡量減少磁盤I/O,為了達到這個目的,磁盤往往不是嚴格按需讀取,而是每次都會預讀。

    即使只需要一個字節,磁盤也會從這個位置開始,順序向後讀取一定長度的數據放入內存。

    這樣做的理論依據是計算機科學中著名的局部性原理:當一個數據被用到時,其附近的數據也通常會馬上被使用。

    預讀的長度一般為頁(page)的整倍數。

    頁是計算機管理存儲器的邏輯塊,硬件及操作系統往往將主存和磁盤存儲區分割為連續的大小相等的塊

    每個存儲塊稱為一頁(在許多操作系統中,頁得大小通常為4k),主存和磁盤以頁為單位交換數據。

    當程序要讀取的數據不在主存中時,會觸發一個缺頁異常,此時系統會向磁盤發出讀盤信號

    磁盤會找到數據的起始位置並向後連續讀取一頁或幾頁載入內存中,然後異常返回,程序繼續運行。

  6.數據庫索引所采用的數據結構B-/+Tree及其性能分析

    6.1、B-tree性能分析

      a.數據庫索引是存儲到磁盤的而我們又一般以使用磁盤I/O次數來評價索引結構的優劣

      b.根據B-Tree的定義,可知檢索一次最多需要訪問h-1個節點(根節點常駐內存)

      c.數據庫系統的設計者巧妙利用了磁盤預讀原理,將一個節點的大小設為等於一個頁,這樣每個節點只需要一次I/O就可以完全載入

      d.為了達到這個目的,在實際實現B-Tree還需要使用如下技巧:每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也存儲在一個頁裏,加之計算機存儲分配都是按頁對齊的,就實現了一個node只需一次I/O

      e.B-Tree中一次檢索最多需要h-1次I/O(根節點常駐內存),漸進復雜度為O(h)=O(logdN)

      f.一般實際應用中,出度d是非常大的數字,通常超過100,因此h非常小(通常不超過3)

    綜上所述,如果我們采用B-Tree存儲結構,搜索時I/O次數一般不會超過3次,所以用B-Tree作為索引結構效率是非常高的。

    6.2、B+tree性能分析

      a.從上面介紹我們知道,B樹的搜索復雜度為O(h)=O(logdN),所以樹的出度d越大,深度h就越小,I/O的次數就越少

      b.B+Tree恰恰可以增加出度d的寬度,因為每個節點大小為一個頁大小,所以出度的上限取決於節點內key和data的大小

      c.由於B+Tree內節點去掉了data域,因此可以擁有更大的出度,從而擁有更好的性能

    6.3、B+tree的查找過程

      技術分享圖片

      a.如果要查找數據項29,那麽首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO

      b.在內存中用二分查找確定29在17和35之間,鎖定磁盤塊1的P2指針,內存時間因為非常短(相比磁盤的IO)可以忽略不計

      c.通過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁盤加載到內存,發生第二次IO

      d.29在26和30之間,鎖定磁盤塊3的P2指針

      e.通過指針加載磁盤塊8到內存,發生第三次IO

      f.同時內存中做二分查找找到29,結束查詢,總計三次IO

      結論:真實的情況是,3層的b+樹可以表示上百萬的數據,如果上百萬的數據查找只需要三次IO,性能提高將是巨大的,如果沒有索引,每個數據項都要發生一次IO,那麽總共需要百萬次的IO,顯然成本非常非常高

四、Mysql索引實現

在MySQL中,索引屬於存儲引擎級別的概念,不同存儲引擎對索引的實現方式是不同的,本文主要討論MyISAM和InnoDB兩個存儲引擎的索引實現方式。

  

  1、MyISAM實現索引

      1. MyISAM引擎使用B+Tree作為索引結構,葉節點的data域存放的是數據記錄的地址。

      2. MyISAM中索引檢索的算法為首先按照B+Tree搜索算法搜索索引。

      3. 如果指定的Key存在,則取出其data域的值,然後以data域的值為地址,讀取相應數據記錄。

      4. MyISAM的索引方式也叫做“非聚集”的,之所以這麽稱呼是為了與InnoDB的聚集索引區分。

  2.MyISAM索引實現原理圖

    技術分享圖片

    1.這裏設表一共有三列,假設我們以Col1為主鍵,則上圖是一個MyISAM表的主索引(Primary key)示意

    2.可以看出MyISAM的索引文件僅僅保存數據記錄的地址

    

  3. InnoDB索引實現

  雖然InnoDB也使用B+Tree作為索引結構,但具體實現方式卻與MyISAM截然不同。

   

    1、區別1:InnoDB的數據文件本身就是索引文件

      1. 從上文知道,MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。

      2. 而在InnoDB中,表數據文件本身就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。

      3. 這個索引的key是數據表的主鍵,因此InnoDB表數據文件本身就是主索引。

    技術分享圖片

    

      說明:

        可以看到葉節點包含了完整的數據記錄,這種索引叫做聚集索引。

        因為InnoDB的數據文件本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有)

        如果沒有顯式指定,則MySQL系統會自動選擇一個可以唯一標識數據記錄的列作為主鍵

        如果不存在這種列,則MySQL自動為InnoDB表生成一個隱含字段作為主鍵,這個字段長度為6個字節,類型為長整形。

     技術分享圖片

    2、區別2:InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址

      1. 換句話說,InnoDB的所有輔助索引都引用主鍵作為data域。

      2. 這裏以英文字符的ASCII碼作為比較準則。

      3. 聚集索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引

      4. 首先檢索輔助索引獲得主鍵,然後用主鍵到主索引中檢索獲得記錄。

    3、為什麽不建議使用過長的字段作為主鍵

      1. 例如知道了InnoDB的索引實現後,就很容易明白為什麽不建議使用過長的字段作為主鍵

      2. 因為所有輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。

      3. 再例如,用非單調的字段作為主鍵在InnoDB中不是個好主意,因為InnoDB數據文件本身是一顆B+Tree,

      4. 非單調的主鍵會造成在插入新記錄時數據文件為了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用自增字段作為主鍵則是一個很好的選擇。

五、索引使用策略

  1、聯合索引(復合索引)

      1. 聯合索引其實很簡單,相對於一般索引只有一個字段,聯合索引可以為多個字段創建一個索引

      2. 它的原理也很簡單,比如,我們在(a,b,c)字段上創建一個聯合索引,則索引記錄會首先按照A字段排序,然後再按照B字段排序然後再是C字段

      3. 其實聯合索引的查找就跟查字典是一樣的,先根據第一個字母查,然後再根據第二個字母查

      4. 或者只根據第一個字母查,但是不能跳過第一個字母從第二個字母開始查。這就是所謂的最左前綴原理。

      5. 聯合索引的特點就是:

        1)第一個字段一定是有序的
        2)當第一個字段值相等的時候,第二個字段又是有序的,比如下表中當A=2時所有B的值是有序排列的,依次類推,當同一個B值得所有C字段是有序排列的

技術分享圖片
‘‘‘最左前綴原理‘‘‘

#### 1、以下的查詢方式都可以用到索引
‘‘‘
select * from table where a=1;
select * from table where a=1 and b=2;
select * from table where a=1 and b=2 and c=3;
上面三個查詢按照 (a ), (a,b ),(a,b,c )的順序都可以利用到索引,這就是最左前綴匹配。
‘‘‘

#### 2、如果查詢語句是:
‘‘‘
select * from table where a=1 and c=3; 那麽只會用到索引a。
‘‘‘

#### 3、這樣不會用的索引
‘‘‘
select * from table where b=2 and c=3; 因為沒有用到最左前綴a,所以這個查詢是用戶到索引的。
‘‘‘
最左前綴查詢原理

  

  2、前綴索引

      1. 前綴索引就是用列的前綴代替整個列作為索引key,當前綴長度合適時,可以做到既使得前綴索引的選擇性接近全列索引

      2. 同時因為索引key變短而減少了索引文件的大小和維護開銷。

      3. 一般來說以下情況可以使用前綴索引:

        1)字符串列(varchar,char,text等),需要進行全字段匹配或者前匹配。也就是=‘xxx’ 或者 like ‘xxx%’
        2)字符串本身可能比較長,而且前幾個字符就開始不相同。(比如:收件地址、外國人的姓名)

      4. MySQL 前綴索引能有效減小索引文件的大小,提高索引的速度。

      5. 但是前綴索引也有它的壞處:MySQL 不能在 ORDER BY 或 GROUP BY 中使用前綴索引,也不能把它們用作覆蓋索引(Covering Index)。

  3、索引優化策略

      說明:MySQL的優化主要分為結構優化(Scheme optimization)和查詢優化(Query optimization)。

技術分享圖片
# 1、最左前綴匹配原則,上面講到了
# 2、主鍵外鍵一定要建索引
# 3、對 where,on,group by,order by 中出現的列使用索引
# 4、盡量選擇區分度高的列作為索引,區分度的公式是count(distinct col)/count(*),表示字段不重復的比例,
# 5、為較長的字符串使用前綴索引

‘‘‘
# 6、不要過多創建索引, 權衡索引個數與DML之間關系,DML也就是插入、刪除數據操作。
    這裏需要權衡一個問題,建立索引的目的是為了提高查詢效率的,但建立的索引過多,
    會影響插入、刪除數據的速度,因為我們修改的表數據,索引也需要進行調整重建
‘‘‘

‘‘‘
# 7、對於like查詢,”%”不要放在前面。
    SELECT * FROMhoudunwangWHEREunameLIKE‘後盾%‘ -- 走索引 
    SELECT * FROMhoudunwangWHEREunameLIKE "%後盾%" -- 不走索引
‘‘‘

‘‘‘
# 8、查詢where條件數據類型不匹配也無法使用索引 
    字符串與數字比較不使用索引; 
    CREATE TABLEa(achar(10)); 
    EXPLAIN SELECT * FROMaWHEREa="1" – 走索引 
    EXPLAIN SELECT * FROM a WHERE a=1 – 不走索引 
    正則表達式不使用索引,這應該很好理解,所以為什麽在SQL中很難看到regexp關鍵字的原因
‘‘‘
索引優化方案 技術分享圖片
建索引原則:

1.最左前綴匹配原則,非常重要的原則,mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就停止匹配,
比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的,
如果建立(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調整。

2.=和in可以亂序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意順序,
mysql的查詢優化器會幫你優化成索引可以識別的形式

3.盡量選擇區分度高的列作為索引,區分度的公式是count(distinct col)/count(*),表示字段不重復的比例,
比例越大我們掃描的記錄數越少,唯一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,
那可能有人會問,這個比例有什麽經驗值嗎?使用場景不同,這個值也很難確定,
一般需要join的字段我們都要求是0.1以上,即平均1條掃描10條記錄

4.索引列不能參與計算,保持列“幹凈”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,
原因很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,需要把所有元素都應用函數才能比較,
顯然成本太大。所以語句應該寫成create_time = unix_timestamp(’2014-05-29’);

5.盡量的擴展索引,不要新建索引。比如表中已經有a的索引,現在要加(a,b)的索引,那麽只需要修改原來的索引即可
索引建立原則

   

mysql索引原理以及優化