1. 程式人生 > >淺談SQL優化入門:3、利用索引

淺談SQL優化入門:3、利用索引

其它 工作 書籍 htm color 之前 index mage 即使


0、寫在前面的話

關於索引的內容本來是想寫的,大概收集了下資料,發現並沒有想象中的簡單,又不想總結了,糾結了一下,決定就大概寫點淺顯的,好吧,就是懶,先挖個淺坑,以後再挖深一點。最基本的使用很簡單,直接就寫在這裏吧。
索引是眾所周知的可以提高查詢的速度,且針對的是具體的字段,使用方式為( 不具體指明則建立非聚集索引):
  1. CREATE INDEX <索引名> ON <表名(關系名)>;
  2. e.g.
  3. CREATE INDEX yearIndex ON movie(year);

而撤銷索引:
  1. DROP INDEX <索引名> ON <表名>;
  2. e.g.
  3. DROP INDEX yearIndex ON movie
    ;



1、索引

1.1 索引的概念

我們在數據庫查詢過程中,即使滿足給定條件的記錄很少,也需要把整個表關系掃描一遍,當關系非常大時,其開銷是很大的。
例如,在電影表中查詢迪斯尼公司2000年制作的電影:
  1. SELECT *
  2. FROM movie
  3. WHERE studioName=‘Disney‘ AND year=2000;

假設電影表中有10000條記錄,其中2000年制作的電影有200個,這之中制作公司為迪斯尼的只有10個。如果不采取措施,要實現該查詢,我們就要把10000個記錄都挨個進行檢查是否滿足條件。如果有某種方法能讓我們只取出年份為2000年的200條記錄,然後再去找制作公司為迪斯尼的,顯然效率要高很多。
確實有這樣的方法,就叫做索引,它是一種為表中的給定字段提供存取路徑的數據結構

所以什麽是索引?上面這個解釋完全不明白,我們還是形象一點說明,網友 elysee 在它的博客《數據庫優化實踐【索引篇】》中講了一個故事,非常形象,但我總理解起來有些不妥,就按照自己的意思修改了一下,故事如下:
技術分享

很久以前,在一個古城的的大圖書館中珍藏有成千上萬本書籍,但書架上的書沒有按任何順序擺放,因此每當有人詢問某本書時,圖書管理員只有挨個尋找,每一次都要花費大量的時間。更糟的是圖書館的圖書越來越多,圖書管理員的工作變得異常痛苦。
有一天,圖書館來了一個聰明的小夥子,他看到圖書管理員的痛苦工作後,想出了一個辦法,他說:“你把所有書架,按照英文字母分成26個部分,每本書根據書名的字母順序,放到相應的書架上去。比如《阿凡提》就放在A書架中,如果有《阿凡達》,也放在A書架,且根據書名字母順序《阿凡達》(afd)放在《阿凡提》(aft)前面”,這樣一來,如果有人指定了書籍的名字,
那麽圖書管理員很快就可以找到它的位置了。
(圖書按照書名的字母順序放在對應的字母書架上,就像創建聚集索引,即表中的所有行會在文件系統上根據書名進行物理排序,當查詢表中任一行時,數據庫首先使用聚集索引找到對應的數據頁,就像首先找到對應的字母書架一樣;然後按順序就可以找到目標行,就像找到書架上的書一樣)
於是圖書管理員開始分類整理放置,為此他花了整整一周時間,最後,他發現找書的效率確實大大提高了。
(在一個表上只能創建一個聚集索引,就像書只能按一種規則擺放一樣)
但問題並未完全解決,有很多人想看某個作者的所有書,圖書管理員無奈又只有掃描所有圖書的作者,進行挨個尋找,時間又變得太長了,因此他向那個聰明的小夥子求助。
(這就好像你給book表增加了索引bookName,但除此之外沒有建立其它索引,當使用bookAuthor進行檢索時,數據庫引擎又只要進行全表掃描,逐個尋找)
聰明的小夥告訴圖書管理員,那就創建一個目錄文檔好了,文檔中將圖書按照作者分類,將書籍重新排列,並把作者的圖書和圖書對應的書架位置一起記錄下來就好了,不用去動圖書真正的位置。這樣,一旦有人指定作者,那麽根據作者分類的目錄文檔,就可以找到該作者的書了。
於是圖書管理員又花費時間趕緊整理了一個目錄文檔,果然有效,然後他又開始了新的思考,讀者可能還會根據圖書的其它屬性來找書,如書的類型,是小說?詩歌?還是其他什麽,於是他用這個辦法為書的種類創建了目錄,現在可以根據書名、作者和種類迅速找到圖書了,圖書管理員的工作變得輕松了。
故事到這裏就結束了。其中上面提到了個概念,叫聚集索引,索引順序和物理順序相同,只能有一個,也就是這裏的書名;相對的,還有非聚集索引,可以有多個,索引順序和物理順序沒有關系,也就是後來的作者目錄,種類目錄。

1.2 索引的存儲

要理解這兩者的區別和含義,需要先來聊聊索引的存儲。首先,一條索引記錄包含:
  • 鍵值(即你定義索引時指定的所有字段的值)
  • 邏輯指針(指向數據頁或者另一索引頁)

另外,索引不論是聚集索引還是非聚集索引,都是數據庫另外開辟的空間,並不是依附在原有數據上的。所以當創建索引時,數據庫系統會分配一個索引頁,每當你往表中插入一行數據,數據庫系統也將插入一行索引記錄。此時的索引頁是根節點,如果滿了,則會進行分裂,將原來指向數據頁的邏輯指針,更換為指向子索引頁的邏輯指針(如下圖)。
技術分享

1.3 索引的類型

1.3.1 聚集索引

所謂聚集索引,就是確定表中數據的物理順序,如上面圖書的按書名排列,又或者手機電話簿的聯系人按姓氏排列等。它規定了數據在表中的物理存儲順序,因此一個表只能包含一個聚集索引。也就是,索引存儲順序和數據行的存儲順序是一致的。

技術分享

如上圖,我們在名字字段上建立聚集索引,當需要在根據此字段查找特定的記錄時,數據庫系統會根據特定的系統表查找的此索引的根,然後根據指針查找下一個,直到找到。例如我們要查詢“green”,由於它介於[bennet,karsen],據此我們找到了索引頁1007,在該頁中“green”介於[greane, hunter]間,據此我們找到葉結點1133(也即數據結點),並最終在此頁中找以了目標數據行。

1.3.2 非聚集索引

我們說聚集索引存儲記錄是物理上連續存在,而非聚集索引是邏輯上的連續,所以在物理上它就沒有所謂的順序可言,也和物理存儲順序無關。
技術分享

如上圖,可以看到,非聚集索引的索引記錄結構發生了一定的變化,它包括:
  • 索引字段值(鍵值)
  • 數據頁的頁指針,以及指針偏移量(相對聚集索引,新增的部分)
  • 下一個索引頁的指針

因為聚集索引是有順序的,所以我們最終只需要指向索引頁,按順序就能找到,也正是如此,聚集索引最終索引頁存儲的是頁指針,而不是行指針
而非聚集索引,因為無序,那麽意味著非聚集索引要為每一個數據行存儲一條索引記錄,才能進行準確查詢。這裏就有了“數據頁的頁指針,和指針偏移量”,類似於數據行的坐標,存儲在索引記錄中。也就是說,非聚集索引存儲的是鍵值和其對應的數據坐標,又為了索引分頁,所以也包含第三個部分,用來存儲下一個索引頁指針。
(就像之前那個故事,如果書籍已經按名字排序,你要找《朝花夕拾》,你知道在Z書架即可;如果書籍還是亂糟糟沒有排序,而你手裏有個作者分類的目錄文檔,那你要找魯迅的《朝花夕拾》,意味著文檔上就必須記錄書籍具體的位置了)



2、漢語字典的例子

如果你還不明白關於“聚集索引和非聚集索引的區別”,這裏還有一個形象的例子:
技術分享
1)聚集索引漢語字典,其正文本身就是一個聚集索引。
比如,我們要查“安”字,就會很自然地翻開字典的前幾頁,因為“安”的拼音是“an”,而按照拼音排序漢字的字典是以英文字母“a”開頭並以“z”結尾的,那麽“安”字就自然地排在字典的前部。如果您翻完了所有以“a”開頭的部分仍然找不到這個字,那麽就說明您的字典中沒有這個字。
同樣的,如果查“張”字,那您也會將您的字典翻到最後部分,因為“張”的拼音是“zhang”。也就是說,字典的正文部分本身就是一個目錄,你不需要再去查其他目錄來找到您需要找的內容。正文內容本身就是一種按照一定規則排列的目錄稱為“聚集索引”。

2)非聚集索引如果您認識某個字,您可以快速地從自動中查到這個字。但您也可能會遇到您不認識的字,不知道它的發音,這時候,您就不能按照剛才的方法找到您要查的字,而需要去根據“偏旁部首”查到您要找的字,然後根據這個字後的頁碼直接翻到某頁來找到您要找的字。
但您結合“部首目錄”和“檢字表”而查到的字的排序並不是真正的正文的排序方法,比如您查“張”字,我們可以看到在查部首之後的檢字表中“張”的頁碼是672頁,檢字表中“張”的上面是“馳”字,但頁碼卻是63頁,“張”的下面是“弩”字,頁面是390頁。很顯然,這些字並不是真正的分別位於“張”字的上下方,現在您看到的連續的“馳、張、弩”三字實際上就是他們在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我們可以通過這種方式來找到您所需要的字,但它需要兩個過程,先找到目錄中的結果,然後再翻到您所需要的頁碼。



3、簡單總結

  • 索引可以提高查詢效率
  • 聚集索引一個表只能有一個,而非聚集索引一個表可以存在多個
  • 聚集索引存儲記錄是物理上連續存在,而非聚集索引是邏輯上的連續,物理存儲並不連續

另外,眾所周知的是,索引確實可以提高查詢速度,但會影響插入、刪除和更新的效率,正是因為數據變化的同時,索引也必須進行更新維護,消耗性能。所以不能盲目地創建索引,而是合理地創建和使用。



4、參考鏈接

  • SQL索引一步到位
  • 數據庫優化實踐【索引篇】
  • 數據庫進階系列之一:漫談數據庫索引
  • 聚集索引和非聚集索引的區別


淺談SQL優化入門:3、利用索引