Mysql索引雜談

結論:索引是把雙刃劍,可以提高數據庫性能,也會影響數據庫性能

  1. 利:
  • 索引加快數據查詢速度,提高數據庫查詢性能。
  1. 弊:
  • 數據庫中索引是以文件的方式存儲的,需要用的時候讀取到內存中,因此索引的I/O操作會影響數據庫的性能;
  • 此外插入和更新操作會更改索引,因此會影響數據庫插入和更新的性能,并且索引會占用一定的磁盤空間,使數據庫變大。

背景知識

  • 索引的本質: MySQL官方定義為:索引是幫助MySQL高效獲取數據的數據結構,所以索引的本質是一種數據結構。常用的數據結構有:集合,線性結構,樹,圖等。
  • 索引的目的:數據庫查詢是數據庫最主要的功能之一,索引的目的在于加快數據庫的查詢速度,從而提高數據庫的使用性能。
  • 最基本的查詢算法是順序查找,復雜度為O(n),在數據量較大的情況下性能較差;其次有二分查找,復雜度為O(logn),在數據量大的情況下性能較好。不同的查詢算法需要適配不同的數據結構,順序查找主要針對的是線性結構;而二分查找主要適用于二叉查找樹。
  • 在數據庫中,數據本身的組織結構不可能完全滿足各種數據結構(比如,二叉查找樹需要排序),所以數據庫需要在數據之外,還維護滿足特定查找算法的數據結構,這些數據結構以某種方式指向具體的數據,通過查找這些數據結構,就可以快速的查詢數據。這種數據結構,就是索引。一個簡單的示例如下:
    圖1.png
  • 圖1中展示了一種簡單的索引方式,左邊記錄是物理地址,存放在磁盤上,為了加快col2的查找,可以維護一個右邊所示的二叉查找樹,每個節點包含索引鍵值和對應記錄在磁盤上的物理地址,這樣通過查找樹就可以在O(logn)的復雜度內獲取相應的數據。(圖中示例使用了二叉樹做為索引是數據結構,其實是一種不好的結構,后續拓展中會說明)

Mysql索引

在Mysql中,索引是屬于存儲引擎級別的概念,因此不同的存儲引擎對索引的實現方式是不同的,主要常用的是MyISAM和InnoDB兩個存儲引擎。(MyISAM提供表級鎖,適用于基本上是查詢操作的數據庫;InnoDB提供行級鎖,適用于更新,插入較頻繁的表)

MyISAM和InnoDB兩個存儲引擎主要使用B+樹做為索引。B+樹是一個樹形的數據結構,特點是:

  • 每個節點的指針上限是2d
  • 內節點不存儲data,只存儲key;只有葉子節點才存儲數據

聚簇索引和非聚簇索引區別:

聚簇索引和非聚簇索引
MyISAM索引結構:

MyISAM存儲引擎中數據文件和索引文件是分離的。
MyISAM的索引主要分為主索引和輔助索引:MyISAM索引方式也稱為“非聚集”索引

  • 主索引(primary key):以主鍵做為索引,因此key是唯一的;索引示例圖如下所示:


    圖2

    圖2中按照主鍵構造了個B+樹,葉子節點中存儲了主鍵記錄在磁盤上的地址,因此通過O(logn)的復雜度就可以索引到記錄。

  • 輔助索引(secondary key):輔助索引以非主鍵做為索引,因此key是可以重復的


    圖3

    圖3中的輔助索引B+樹,葉子節點的內容中也保存了磁盤上記錄的地址,然后讀取相應的數據記錄。

InnoDB索引結構:

InnoDB存儲引擎也分為主索引和輔助索引:InnoDB索引方式也稱為聚集索引
InnoDB和MyISAM最大的區別是:InnoDB數據文件本身就是索引文件。因為InnoDB的數據文件本身是按主鍵聚集的,所以InnoDB要求表必須有主鍵,如果沒有顯示指定主鍵,InnoDB會自動選擇可以唯一標識數據記錄的列做主鍵;如果不存在,則表生成一個隱含字段作為主鍵(字段為6個字節,長整形)。

  • 主索引:主鍵做索引


    圖4

    數據記錄在葉子節點中,通過查找直接訪問查詢數據。

  • 輔助索引:非主鍵做索引


    圖5

    輔助索引的葉子節點中值存儲索引鍵值和主鍵值,然后通過主鍵值在主索引中進行搜索數據。

索引總結

  • 不要使用過長的字段做為索引,過長的字段會使索引的數據結構變大,占用更多的空間,因此影響數據庫的性能。(索引做為文件保存在磁盤上,當需要查詢索引的時候會從文件讀取,因此過長的字段會影響索引整體的I/O性能)
  • 在數據庫中盡量使用單調增的字段做為主鍵,因為索引本身是一個B+樹,如果字段是非單調增的,則插入操作會頻繁的分裂B+樹來調增數據的有序性和平衡性,效率十分低下。

索引優化策略

MySQL的優化主要有結構優化和查詢優化。索引策略屬于結構優化的范疇。下面使用Mysql官方提供的employees數據庫示例來演示索引策略。

employees數據庫安裝:

數據下載:https://launchpad.net/test-db/employees-db-1/1.0.6
參考網頁:http://dev.mysql.com/doc/employee/en/employees-installation.html
Mysql5.7版本可能會報錯,參考網頁:http://stackoverflow.com/questions/36322903/error-1193-when-following-employees-database-install-tutorial-with-mysql-5-7-1

圖6
最左前綴匹配原理

要想高效的使用索引,首先需要知道查詢操作怎么使用索引,目前常用的是兩種索引:單列索引,組合索引(多列順序組合)。

以employees數據庫中的titles為例,索引如下圖所示:

  • 圖7

從圖7中可以看出,titles存在兩個索引,第一個是<emp_no,title,from_date>元組的組合索引(主索引);第二個是單列的輔助索引(emp_no)。下面通過語句看索引使用的幾種情況:

  • 全列匹配:

    • 順序查詢索引:


      圖8

      從圖中可以看出,查詢條件的順序和索引一致,全列匹配使用了主索引。

    • 亂序查詢索引:


      圖9

      從圖中看出,不管查詢條件的順序如何,全列匹配都會使用索引,因為數據庫本身會把sql語句進行優化來匹配索引。

  • 部分列匹配(最左前綴匹配):

    • 第一列查詢條件:



      圖10

      從圖10中可以看出,如果按第一列進行匹配,后續的列也必須按照順序排列,不然只會使用第一列來索引,第一張圖片,用來兩列做為索引;第二張圖片只匹配第一列(兩張圖片key_len不同)。

    • 非第一列查詢:



      圖11

      從圖11中可以看出,如果查詢條件中不含有第一列,則不能使用索引。

  • 優化示例:
    當我們使用emp_no和from_date來進行索引時,只會用到emp_no的最左匹配索引,索引語句如下所示:

    select * from titles where emp_no=10009 and from_date='1990-02-18';

但是這只用到了emp_no這個字段的索引,因為缺少中間的title字段,我們使用distinct,發現title只有固定幾個值,因此,可以在sql中補全title來進行全列匹配索引。

圖12

索引選擇性

既然索引可以加快查詢速度,是不是意味著只要查詢語句需要,就可以建上索引?答案是否定的。因為索引雖然可以加快查詢的速度,但索引本身會占用存儲空間,并且數據庫進行DML操作時會調整索引的架構,同時mysql也會消耗資源來維護索引,因此索引并不是越多越好。

一般有兩種情況不建議添加索引:

  • 表的記錄少,很少的數據就沒有必要建立索引,一般來說,超過2000條記錄可以考慮建立索引。
  • 索引的選擇性較低,也不建議建立索引。索引的選擇性是指不重復的索引值與表記錄數的比值。(簡單的理解就是:如果索引的列存在大量相同的值,那么它的索引是沒有意義的)
    示例:比如employees.titles表中title列只有固定的幾個值:
    圖13

    如圖13中所示,title列的索引選擇性很低,因此沒有必要建立一個單獨的title列索引。

我們再看一個示例:employees.employees表中的索引如下圖所示,從圖中看出employees表中只有一個主鍵索引,如果我們需要按照名字(first_name,last_name)來查詢數據,在數據量較大的情況下速度會很慢,因此我們需要建立名字查詢的索引。


圖14

首先看下按名字索引的選擇性:


圖15

從圖15中可以看出,如果單純使用first_name字段進行索引,重復率太高,索引的選擇性非常低,因此使用first_name和last_name的聯合索引,但是這兩個聯合索引是不是最好的?答案是否定的,因為這兩個字段是啥字符串型,如果使用聯合索引,則索引的數據結構會變得比較龐大,占用大量的磁盤空間,導致頻繁的磁盤I/O,反而影響數據庫的性能。因此可以在索引上做下優化:
圖16

當只取last_name前4位時(前綴索引),索引的選擇性已然達到了0.9以上,因此是一個性能不錯的索引,此外該索引的磁盤空間要比全列索引占用量小,綜合性能會更好。前綴索引兼顧了速度與索引大小,但其缺點是不能用于order by和group by操作。

拓展:

  • 為啥Mysql索引不使用紅黑樹?
  • 為何Mysql索引傾向于B+樹而不是B-樹?

兩個問題的答案都在于使用B+樹做為索引,可以減少索引的磁盤I/O性能。從前面我們可知,索引是一個數據結構,存放在磁盤中,當需要使用索引時,從磁盤讀取到內存中,然后在內存中進行索引查詢數據。因此索引的查找過程要產生磁盤I/O消耗,相對于內存存取,I/O的速度要比內存低好幾個數量級,所以一個索引的優劣性能可以通過索引文件的磁盤I/O消耗來衡量,如果磁盤I/O消耗越低,這就是一個越高效的索引。(所以B+優于紅黑樹和B-樹的點就在于,B+樹的數據結構有更小的磁盤I/O消耗)。下面詳細介紹:

  • 主存存取原理:

    • 主存讀取:將地址信號放在地址總線上傳給主存,主存讀取信號,然后到指定主存位置讀取數據,再傳輸到數據總線上,供其他部件讀取
    • 主存寫入:將要寫入的數據放入數據總線中,寫入的地址放入地址總線上,然后主存讀取兩個總線內容,把數據放入相應地址上。
  • 磁盤存取原理:

    • 磁盤主要由大小相同且同軸的盤片組成,磁盤可以轉動,磁盤的一頭有一個固定的磁頭,用于讀取磁盤的數據,磁頭可以只能沿盤片半徑方向運動。盤片上一個個同心圓叫做磁道,磁盤分成若干個扇區,如果要讀取某一扇區的內容,需要先將磁頭移動到相應的磁道上,這叫尋道時間;再將盤片旋轉到相應的扇區,這叫旋轉時間。通常情況下磁盤的讀取主要是尋道時間。
    • 磁盤本身的速度相比于主存,cpu而言是非常慢的,為了提交效率,就需要盡量減少磁盤的I/O次數,因此磁盤在讀取一個扇區后,不會直接停止,而是向后多讀取一些內容,這就是磁盤預讀。磁盤預讀的理論依據是局部性原理,當程序用到一個數據時,它周圍的數據也將會被使用到。磁盤預讀的長度一般為頁的整數倍(頁的相關知識請參考Linux內存管理相關知識),主存和磁盤以頁為交換單位。
  • B+樹的索引:

    • 在Mysql索引的設計中,當索引每次新建一個節點時,就會創建一個頁,因此一個節點就存放在一個頁上,磁盤的一次I/O和主存交換一個頁(不考慮預讀),也就是一次可以讀取一個節點,如果使用紅黑樹,可以發現紅黑樹是二叉樹,它的高度要遠遠高于B+/B-樹,因此每次讀取一個節點進行查找,需要大量的磁盤I/O操作才能查詢到數據,因為樹的查詢性能和樹的高度線性相關,因此B+/B-樹要優于紅黑樹。當考慮預讀的話,紅黑樹是二叉樹,兄弟節點只有一個;而B+/B-樹的兄弟節點有多個,可以充分發揮磁盤預讀的功能。
      圖17
    • 由于B+樹把數據放在葉子節點上,因此B+樹在非葉子節點的一個節點上可以存放更多的鍵值;而B-樹把數據和鍵值放在一起,則一個節點放置的鍵值相對較少,也就是一個頁中存放較少的鍵值,因此使用B+樹會達到更少的磁盤I/O次數,因此性能更好。

B+樹磁盤索引過程:


圖18

B+樹更適合操作系統文件和數據庫文件的索引。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容