SQL Server中的彙集索引(clustered index) 和 非彙集索引 (non-clustered index)

本文轉載自  http://blog.csdn.net/ak913/article/details/8026743面試

面試時常常問到的問題:數據庫

1. 什麼是聚合索引(clustered index) / 什麼是非聚合索引(nonclustered index)? 
2. 聚合索引和非聚合索引有什麼區別? 性能

深刻淺出理解索引結構      

實際上,您能夠把索引理解爲一種特殊的目錄。微軟的SQL SERVER提供了兩種索引:彙集索引(clustered index,也稱聚類索引、簇集索引)和非彙集索引(nonclustered index,也稱非聚類索引、非簇集索引)。下面,咱們舉例來講明一下彙集索引和非彙集索引的區別:      

其實,咱們的漢語字典的正文自己就是一個彙集索引。好比,咱們要查"安"字,就會很天然地翻開字典的前幾頁,由於"安"的拼音是"an",而按照拼音排序漢字的字典是以英文字母"a"開頭並以"z"結尾的,那麼"安"字就天然地排在字典的前部。若是您翻完了全部以"a"開頭的部分仍然找不到這個字,那麼就說明您的字典中沒有這個字;一樣的,若是查"張"字,那您也會將您的字典翻到最後部分,由於"張"的拼音是"zhang"。也就是說,字典的正文部分自己就是一個目錄,您不須要再去查其餘目錄來找到您須要找的內容。      

咱們把這種正文內容自己就是一種按照必定規則排列的目錄稱爲"彙集索引"。      

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

咱們把這種目錄純粹是目錄,正文純粹是正文的排序方式稱爲"非彙集索引"。      

經過以上例子,咱們能夠理解到什麼是"彙集索引"和"非彙集索引"。      

進一步引伸一下,咱們能夠很容易的理解:每一個表只能有一個彙集索引 ,由於目錄只能按照一種方法進行排序。      

(二)什麼時候使用匯集索引或非彙集索引      

下面的表總結了什麼時候使用匯集索引或非彙集索引(很重要)。        
   

動做描述                        使用匯集索引                       使用非彙集索引     
   

列常常被分組排序              應                                       應     

返回某範圍內的數據          應                                       不該        

一個或極少不一樣值           不該                                     不該     

小數目的不一樣值                應                                       不該     

大數目的不一樣值              不該                                      應     

頻繁更新的列                 不該                                      應     

外鍵列                            應                                        應     

主鍵列                            應                                        應     

頻繁修改索引列             不該                                       應     
    

事實上,咱們能夠經過前面彙集索引和非彙集索引的定義的例子來理解上表。如:返回某範圍內的數據一項。好比您的某個表有一個時間列,剛好您把聚合索引創建在了該列,這時您查詢2004年1月1日至2004年10月1日之間的所有數據時,這個速度就將是很快的,由於您的這本字典正文是按日期進行排序的,聚類索引只須要找到要檢索的全部數據中的開頭和結尾數據便可;而不像非彙集索引,必須先查到目錄中查到每一項數據對應的頁碼,而後再根據頁碼查到具體內容。      


(三)結合實際,談索引使用的誤區      

理論的目的是應用。雖然咱們剛纔列出了什麼時候應使用匯集索引或非彙集索引,但在實踐中以上規則卻很容易被忽視或不能根據實際狀況進行綜合分析。下面咱們將根據在實踐中遇到的實際問題來談一下索引使用的誤區,以便於你們掌握索引創建的方法。      

一、主鍵就是彙集索引      

這種想法是極端錯誤的,是對彙集索引的一種浪費。雖然SQL SERVER默認是在主鍵上創建彙集索引的。      

一般,咱們會在每一個表中都創建一個ID列,以區分每條數據,而且這個ID列是自動增大的,步長通常爲1。咱們的這個辦公自動化的實例中的列Gid就是如此。此時,若是咱們將這個列設爲主鍵,SQL SERVER會將此列默認爲彙集索引。這樣作有好處,就是可讓您的數據在數據庫中按照ID進行物理排序,但筆者認爲這樣作意義不大。      

顯而易見,彙集索引的優點是很明顯的,而每一個表中只能有一個彙集索引的規則,這使得彙集索引變得更加珍貴。      

從咱們前面談到的彙集索引的定義咱們能夠看出,使用匯集索引的最大好處就是可以根據查詢要求,迅速縮小查詢範圍,避免全表掃描。在實際應用中,由於ID號是自動生成的,咱們並不知道每條記錄的ID號,因此咱們很難在實踐中用ID號來進行查詢。這就使讓ID號這個主鍵做爲彙集索引成爲一種資源浪費。其次,讓每一個ID號都不一樣的字段做爲彙集索引也不符合"大數目的不一樣值狀況下不該創建聚合索引"規則;固然,這種狀況只是針對用戶常常修改記錄內容,特別是索引項的時候會負做用,但對於查詢速度並無影響。      

    

在辦公自動化系統中,不管是系統首頁顯示的須要用戶簽收的文件、會議仍是用戶進行文件查詢等任何狀況下進行數據查詢都離不開字段的是"日期"還有用戶自己的"用戶名"。      

一般,辦公自動化的首頁會顯示每一個用戶還沒有簽收的文件或會議。雖然咱們的where語句能夠僅僅限制當前用戶還沒有簽收的狀況,但若是您的系統已創建了很長時間,而且數據量很大,那麼,每次每一個用戶打開首頁的時候都進行一次全表掃描,這樣作意義是不大的,絕大多數的用戶1個月前的文件都已經瀏覽過了,這樣作只能徒增數據庫的開銷而已。事實上,咱們徹底可讓用戶打開系統首頁時,數據庫僅僅查詢這個用戶近3個月來未閱覽的文件,經過"日期"這個字段來限制表掃描,提升查詢速度。若是您的辦公自動化系統已經創建的2年,那麼您的首頁顯示速度理論上將是原來速度8倍,甚至更快。      

在這裏之因此提到"理論上"三字,是由於若是您的彙集索引仍是盲目地建在ID這個主鍵上時,您的查詢速度是沒有這麼高的,即便您在"日期"這個字段上創建的索引(非聚合索引)。下面咱們就來看一下在1000萬條數據量的狀況下各類查詢的速度表現(3個月內的數據爲25萬條):      

(1)僅在主鍵上創建彙集索引,而且不劃分時間段:      

Select gid,fariqi,neibuyonghu,title from tgongwen  用時:128470毫秒(即:128秒)      

(2)在主鍵上創建彙集索引,在fariq上創建非彙集索引:     

select gid,fariqi,neibuyonghu,title from Tgongwen where  fariqi> dateadd(day,-90,getdate())   用時:53763毫秒(54秒)      

(3)將聚合索引創建在日期列(fariqi)上:     

select gid,fariqi,neibuyonghu,title from Tgongwen where  fariqi> dateadd(day,-90,getdate()) 用時:2423毫秒(2秒)      

雖然每條語句提取出來的都是25萬條數據,各類狀況的差別倒是巨大的,特別是將彙集索引創建在日期列時的差別。事實上,若是您的數據庫真的有1000萬容量的話,把主鍵創建在ID列上,就像以上的第一、2種狀況,在網頁上的表現就是超時,根本就沒法顯示。這也是我摒棄ID列做爲彙集索引的一個最重要的因素。     

得出以上速度的方法是:在各個select語句前加:declare @d datetime set @d=getdate()      

並在select語句後加:     

select [語句執行花費時間(毫秒)]=datediff(ms,@d,getdate())     

二、只要創建索引就能顯著提升查詢速度      

事實上,咱們能夠發現上面的例子中,第二、3條語句徹底相同,且創建索引的字段也相同;不一樣的僅是前者在fariqi字段上創建的是非聚合索引,後者在此字段上創建的是聚合索引,但查詢速度卻有着天壤之別。因此,並不是是在任何字段上簡單地創建索引就能提升查詢速度。      

從建表的語句中,咱們能夠看到這個有着1000萬數據的表中fariqi字段有5003個不一樣記錄。在此字段上創建聚合索引是再合適不過了。在現實中,咱們天天都會發幾個文件,這幾個文件的發文日期就相同,這徹底符合創建彙集索引要求的:"既不能絕大多數都相同,又不能只有極少數相同"的規則。由此看來,咱們創建"適當"的聚合索引對於咱們提升查詢速度是很是重要的。      

三、把全部須要提升查詢速度的字段都加進彙集索引,以提升查詢速度      

上面已經談到:在進行數據查詢時都離不開字段的是"日期"還有用戶自己的"用戶名"。既然這兩個字段都是如此的重要,咱們能夠把他們合併起來,創建一個複合索引(compound index)。      

不少人認爲只要把任何字段加進彙集索引,就能提升查詢速度,也有人感到迷惑:若是把複合的彙集索引字段分開查詢,那麼查詢速度會減慢嗎?帶着這個問題,咱們來看一下如下的查詢速度(結果集都是25萬條數據):(日期列fariqi首先排在複合彙集索引的起始列,用戶名neibuyonghu排在後列)      

(1)select gid,fariqi,neibuyonghu,title from Tgongwen where  fariqi>'2004-5-5'  查詢速度:2513毫秒      

(2)select gid,fariqi,neibuyonghu,title from Tgongwen where  fariqi>'2004-5-5' and neibuyonghu='辦公室'   查詢速度:2516毫秒      

(3)select gid,fariqi,neibuyonghu,title from Tgongwen where  neibuyonghu='辦公室'   查詢速度:60280毫秒      

從以上試驗中,咱們能夠看到若是僅用匯集索引的起始列做爲查詢條件和同時用到複合彙集索引的所有列的查詢速度是幾乎同樣的,甚至比用上所有的複合索引列還要略快(在查詢結果集數目同樣的狀況下);而若是僅用複合彙集索引的非起始列做爲查詢條件的話,這個索引是不起任何做用的。固然,語句一、2的查詢速度同樣是由於查詢的條目數同樣,若是複合索引的全部列都用上,並且查詢結果少的話,這樣就會造成"索引覆蓋",於是性能能夠達到最優。同時,請記住:不管您是否常用聚合索引的其餘列,但其前導列必定要是使用最頻繁的列。      

(四)其餘書上沒有的索引使用經驗總結      

一、用聚合索引比用不是聚合索引的主鍵速度快      

下面是實例語句:(都是提取25萬條數據)      

select gid,fariqi,neibuyonghu,reader,title from Tgongwen  where fariqi='2004-9-16' 使用時間:3326毫秒      

select gid,fariqi,neibuyonghu,reader,title from Tgongwen  where gid<=250000 使用時間:4470毫秒      

這裏,用聚合索引比用不是聚合索引的主鍵速度快了近1/4。      

二、用聚合索引比用通常的主鍵做order by時速度快,特別是在小數據量狀況下     

select gid,fariqi,neibuyonghu,reader,title from Tgongwen  order by fariqi 用時:12936      

select gid,fariqi,neibuyonghu,reader,title from Tgongwen  order by gid  用時:18843     

這裏,用聚合索引比用通常的主鍵做order by時,速度快了3/10。事實上,若是數據量很小的話,用匯集索引做爲排序列要比使用非彙集索引速度快得明顯的多;而數據量若是很大的話,如10萬以上,則兩者的速度差異不明顯。      

三、使用聚合索引內的時間段,搜索時間會按數據佔整個數據表的百分比成比例減小,而不管聚合索引使用了多少個      

select gid,fariqi,neibuyonghu,reader,title from Tgongwen  where fariqi>'2004-1-1' 用時:6343毫秒(提取100萬條)     

select gid,fariqi,neibuyonghu,reader,title from Tgongwen  where fariqi>'2004-6-6' 用時:3170毫秒(提取50萬條)      

select gid,fariqi,neibuyonghu,reader,title from Tgongwen  where fariqi='2004-9-16'     

用時:3326毫秒(和上句的結果如出一轍。若是採集的數量同樣,那麼用大於號和等於號是同樣的)      

select gid,fariqi,neibuyonghu,reader,title from Tgongwen  where fariqi>'2004-1-1' and fariqi<'2004-6-6' 用時:3280毫秒      

4 、日期列不會由於有分秒的輸入而減慢查詢速度      

下面的例子中,共有100萬條數據,2004年1月1日之後的數據有50萬條,但只有兩個不一樣的日期,日期精確到日;以前有數據50萬條,有5000個不一樣的日期,日期精確到秒。      

select gid,fariqi,neibuyonghu,reader,title from Tgongwen  where fariqi>'2004-1-1' order by fariqi 用時:6390毫秒      

select gid,fariqi,neibuyonghu,reader,title from Tgongwen  where fariqi<'2004-1-1' order by fariqi 用時:6453毫秒     

(五)其餘注意事項      

"水可載舟,亦可覆舟",索引也同樣。索引有助於提升檢索性能,但過多或不當的索引也會致使系統低效。由於用戶在表中每加進一個索引,數據庫就要作更多的工做。過多的索引甚至會致使索引碎片。      

因此說,咱們要創建一個"適當"的索引體系,特別是對聚合索引的建立,更應精益求精,以使您的數據庫能獲得高性能的發揮。      .net

相關文章
相關標籤/搜索