什麼是聚集索引,什麼是非聚集索引,什麼又是主鍵?

轉載一篇關於SQL索引以及執行效率及優化的文章

用聚集索引
聚集索引確定表中數據的物理順序。聚集索引類似於電話簿,後者按姓氏排列數據。由於聚集索引規定數據在表中的物理存儲順序,因此一個表只能包含一個聚集索引。但該索引可以包含多個列(組合索引),就像電話簿按姓氏和名字進行組織一樣。

聚集索引對於那些經常要搜索範圍值的列特別有效。使用聚集索引找到包含第一個值的行後,便可以確保包含後續索引值的行在物理相鄰。例如,如果應用程序執行的一個查詢經常檢索某一日期範圍內的記錄,則使用聚集索引可以迅速找到包含開始日期的行,然後檢索表中所有相鄰的行,直到到達結束日期。這樣有助於提高此類查詢的性能。同樣,如果對從表中檢索的數據進行排序時經常要用到某一列,則可以將該表在該列上聚集(物理排序),避免每次查詢該列時都進行排序,從而節省成本。

當索引值唯一時,使用聚集索引查找特定的行也很有效率。例如,使用唯一僱員 ID 列 emp_id 查找特定僱員的最快速的方法,是在 emp_id 列上創建聚集索引或 PRIMARY KEY 約束。

說明  如果該表上尚未創建聚集索引,且在創建 PRIMARY KEY 約束時未指定非聚集索引,PRIMARY KEY 約束會自動創建聚集索引。

也可以在 lname(姓氏)列和 fname(名字)列上創建聚集索引,因爲僱員記錄常常是按姓名而不是按僱員 ID 分組和查詢的。
使用非聚集索引
非聚集索引與課本中的索引類似。數據存儲在一個地方,索引存儲在另一個地方,索引帶有指針指向數據的存儲位置。索引中的項目按索引鍵值的順序存儲,而表中的信息按另一種順序存儲(這可以由聚集索引規定)。如果在表中未創建聚集索引,則無法保證這些行具有任何特定的順序。

與使用書中索引的方式相似,Microsoft® SQL Server™ 2000 在搜索數據值時,先對非聚集索引進行搜索,找到數據值在表中的位置,然後從該位置直接檢索數據。這使非聚集索引成爲精確匹配查詢的最佳方法,因爲索引包含描述查詢所搜索的數據值在表中的精確位置的條目。如果基礎表使用聚集索引排序,則該位置爲聚集鍵值;否則,該位置爲包含行的文件號、頁號和槽號的行 ID (RID)。例如,對於在 emp_id 列上有非聚集索引的表,如要搜索其僱員 ID (emp_id),SQL Server 會在索引中查找這樣一個條目,該條目精確列出匹配的 emp_id 列在表中的頁和行,然後直接轉到該頁該行。

多個非聚集索引
有些書籍包含多個索引。例如,一本介紹園藝的書可能會包含一個植物通俗名稱索引,和一個植物學名索引,因爲這是讀者查找信息的兩種最常用的方法。對於非聚集索引也是如此。可以爲在表中查找數據時常用的每個列創建一個非聚集索引。

注意事項
在創建非聚集索引之前,應先了解您的數據是如何被訪問的。可考慮將非聚集索引用於:

包含大量非重複值的列,如姓氏和名字的組合(如果聚集索引用於其它列)。如果只有很少的非重複值,如只有 1 和 0,則大多數查詢將不使用索引,因爲此時表掃描通常更有效。


不返回大型結果集的查詢。


返回精確匹配的查詢的搜索條件(WHERE 子句)中經常使用的列。


經常需要聯接和分組的決策支持系統應用程序。應在聯接和分組操作中使用的列上創建多個非聚集索引,在任何外鍵列上創建一個聚集索引。


在特定的查詢中覆蓋一個表中的所有列。這將完全消除對錶或聚集索引的訪問。

--------------
PRIMARY KEY 約束
表中經常有一個列或列的組合,其值能唯一地標識表中的每一行。這樣的一列或多列稱爲表的主鍵,通過它可強制表的實體完整性。當創建或更改表時可通過定義 PRIMARY KEY 約束來創建主鍵。

一個表只能有一個 PRIMARY KEY 約束,而且 PRIMARY KEY 約束中的列不能接受空值。由於 PRIMARY KEY 約束確保唯一數據,所以經常用來定義標識列。

當爲表指定 PRIMARY KEY 約束時,Microsoft® SQL Server™ 2000 通過爲主鍵列創建唯一索引強制數據的唯一性。當在查詢中使用主鍵時,該索引還可用來對數據進行快速訪問。

如果 PRIMARY KEY 約束定義在不止一列上,則一列中的值可以重複,但 PRIMARY KEY 約束定義中的所有列的組合的值必須唯一。

如下圖所示,titleauthor 表中的 au_id 和 title_id 列組成該表的組合 PRIMARY KEY 約束,以確保 au_id 和 title_id 的組合唯一。

1、什麼是聚集索引和非聚集索引


SQL SERVER提供了兩種索引:聚集索引(clustered index,也稱聚類索引、簇集索引)和非聚集索引(nonclustered index,也稱非聚類索引、非簇集索引)。


其實,我們的漢語字典的正文本身就是一個聚集索引。比如,我們要查“安”字,就會很自然地翻開字典的前幾頁,因爲“安”的拼音是“an”,而按照拼音排序漢字的字典是以英文字母“a”開頭並以“z”結尾的,那麼“安”字就自然地排在字典的前部。如果您翻完了所有以“a”開頭的部分仍然找不到這個字,那麼就說明您的字典中沒有這個字;同樣的,如果查“張”字,那您也會將您的字典翻到最後部分,因爲“張”的拼音是“zhang”。也就是說,字典的正文部分本身就是一個目錄,您不需要再去查其他目錄來找到您需要找的內容。我們把這種正文內容本身就是一種按照一定規則排列的目錄稱爲“聚集索引”。
  如果您認識某個字,您可以快速地從自動中查到這個字。但您也可能會遇到您不認識的字,不知道它的發音,這時候,您就不能按照剛纔的方法找到您要查的字,而需要去根據“偏旁部首”查到您要找的字,然後根據這個字後的頁碼直接翻到某頁來找到您要找的字。但您結合“部首目錄”和“檢字表”而查到的字的排序並不是真正的正文的排序方法,比如您查“張”字,我們可以看到在查部首之後的檢字表中“張”的頁碼是672頁,檢字表中“張”的上面是“馳”字,但頁碼卻是63頁,“張”的下面是“弩”字,頁面是390頁。很顯然,這些字並不是真正的分別位於“張”字的上下方,現在您看到的連續的“馳、張、弩”三字實際上就是他們在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我們可以通過這種方式來找到您所需要的字,但它需要兩個過程,先找到目錄中的結果,然後再翻到您所需要的頁碼。我們把這種目錄純粹是目錄,正文純粹是正文的排序方式稱爲“非聚集索引”。
  通過以上例子,我們可以理解到什麼是“聚集索引”和“非聚集索引”。進一步引申一下,我們可以很容易的理解:每個表只能有一個聚集索引,因爲目錄只能按照一種方法進行排序。

 


2、何時使用聚集索引或非聚集索引
下面的表總結了何時使用聚集索引或非聚集索引(很重要):

動作描述

 使用聚集索引

 使用非聚集索引

 
列經常被分組排序

 應

 應

 
返回某範圍內的數據

 應

 不應

 
一個或極少不同值

 不應

 不應

 
小數目的不同值

 應

 不應

 
大數目的不同值

 不應

 應

 
頻繁更新的列

 不應

 應

 
外鍵列

 應

 應

 
主鍵列

 應

 應

 
頻繁修改索引列

 不應

 應

 


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

 


3、索引是如何工作的?改善SQL語句

  很多人不知道SQL語句在SQL SERVER中是如何執行的,他們擔心自己所寫的SQL語句會被SQL SERVER誤解。比如:


select * from table1 where name=''zhangsan'' and tID > 10000


和執行:


select * from table1 where tID > 10000 and name=''zhangsan''


  一些人不知道以上兩條語句的執行效率是否一樣,因爲如果簡單的從語句先後上看,這兩個語句的確是不一樣,如果tID是一個聚合索引,那麼後一句僅僅從表的10000條以後的記錄中查找就行了;而前一句則要先從全表中查找看有幾個name=''zhangsan''的,而後再根據限制條件條件tID>10000來提出查詢結果。
  事實上,這樣的擔心是不必要的。SQL SERVER中有一個“查詢分析優化器”,它可以計算出where子句中的搜索條件並確定哪個索引能縮小表掃描的搜索空間,也就是說,它能實現自動優化。
  雖然查詢優化器可以根據where子句自動的進行查詢優化,但大家仍然有必要了解一下“查詢優化器”的工作原理,如非這樣,有時查詢優化器就會不按照您的本意進行快速查詢。
  在查詢分析階段,查詢優化器查看查詢的每個階段並決定限制需要掃描的數據量是否有用。如果一個階段可以被用作一個掃描參數(SARG),那麼就稱之爲可優化的,並且可以利用索引快速獲得所需數據。
  SARG的定義:用於限制搜索的一個操作,因爲它通常是指一個特定的匹配,一個值得範圍內的匹配或者兩個以上條件的AND連接。形式如下:


列名 操作符 <常數 或 變量>

 


 


<常數 或 變量> 操作符列名


列名可以出現在操作符的一邊,而常數或變量出現在操作符的另一邊。如:


Name=’張三’

 


價格>5000

 


5000<價格

 


Name=’張三’ and 價格>5000


  如果一個表達式不能滿足SARG的形式,那它就無法限制搜索的範圍了,也就是SQL SERVER必須對每一行都判斷它是否滿足WHERE子句中的所有條件。所以一個索引對於不滿足SARG形式的表達式來說是無用的。
  介紹完SARG後,我們來總結一下使用SARG以及在實踐中遇到的和某些資料上結論不同的經驗:

1、Like語句是否屬於SARG取決於所使用的通配符的類型


如:name like ‘張%’ ,這就屬於SARG

 


而:name like ‘%張’ ,就不屬於SARG。


原因是通配符%在字符串的開通使得索引無法使用。

2、or 會引起全表掃描
  Name=’張三’ and 價格>5000 符號SARG,而:Name=’張三’ or 價格>5000 則不符合SARG。使用or會引起全表掃描。

3、非操作符、函數引起的不滿足SARG形式的語句
  不滿足SARG形式的語句最典型的情況就是包括非操作符的語句,如:NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE等,另外還有函數。下面就是幾個不滿足SARG形式的例子:


ABS(價格)<5000

 


Name like ‘%三’

 


有些表達式,如:

 


WHERE 價格*2>5000

 


SQL SERVER也會認爲是SARG,SQL SERVER會將此式轉化爲:


WHERE 價格>2500/2


但我們不推薦這樣使用,因爲有時SQL SERVER不能保證這種轉化與原始表達式是完全等價的。

4、IN 的作用相當與OR

語句:


Select * from table1 where tid in (2,3)

 


 


Select * from table1 where tid=2 or tid=3


是一樣的,都會引起全表掃描,如果tid上有索引,其索引也會失效。

5、儘量少用NOT

6、exists 和 in 的執行效率是一樣的
  很多資料上都顯示說,exists要比in的執行效率要高,同時應儘可能的用not exists來代替not in。但事實上,我試驗了一下,發現二者無論是前面帶不帶not,二者之間的執行效率都是一樣的。因爲涉及子查詢,我們試驗這次用SQL SERVER自帶的pubs數據庫。運行前我們可以把SQL SERVER的statistics I/O狀態打開:


(1)select title,price from titles where title_id in (select title_id from sales where qty>30)


該句的執行結果爲:

表 ''sales''。掃描計數 18,邏輯讀 56 次,物理讀 0 次,預讀 0 次。
表 ''titles''。掃描計數 1,邏輯讀 2 次,物理讀 0 次,預讀 0 次。


(2)select title,price from titles


       where exists (select * from sales


       where sales.title_id=titles.title_id and qty>30)


第二句的執行結果爲:

表 ''sales''。掃描計數 18,邏輯讀 56 次,物理讀 0 次,預讀 0 次。
表 ''titles''。掃描計數 1,邏輯讀 2 次,物理讀 0 次,預讀 0 次。

我們從此可以看到用exists和用in的執行效率是一樣的。

7、用函數charindex()和前面加通配符%的LIKE執行效率一樣
  前面,我們談到,如果在LIKE前面加上通配符%,那麼將會引起全表掃描,所以其執行效率是低下的。但有的資料介紹說,用函數charindex()來代替LIKE速度會有大的提升,經我試驗,發現這種說明也是錯誤的:
 


select gid,title,fariqi,reader from tgongwen


         where charindex(''刑偵支隊'',reader)>0 and fariqi>''2004-5-5''


用時:7秒,另外:掃描計數 4,邏輯讀 7155 次,物理讀 0 次,預讀 0 次。


select gid,title,fariqi,reader from tgongwen


         where reader like ''%'' + ''刑偵支隊'' + ''%'' and fariqi>''2004-5-5''


用時:7秒,另外:掃描計數 4,邏輯讀 7155 次,物理讀 0 次,預讀 0 次。

8、union並不絕對比or的執行效率高
  我們前面已經談到了在where子句中使用or會引起全表掃描,一般的,我所見過的資料都是推薦這裏用union來代替or。事實證明,這種說法對於大部分都是適用的。


select gid,fariqi,neibuyonghu,reader,title from Tgongwen


          where fariqi=''2004-9-16'' or gid>9990000


用時:68秒。掃描計數 1,邏輯讀 404008 次,物理讀 283 次,預讀 392163 次。


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


union


select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid>9990000


用時:9秒。掃描計數 8,邏輯讀 67489 次,物理讀 216 次,預讀 7499 次。

看來,用union在通常情況下比用or的效率要高的多。

  但經過試驗,筆者發現如果or兩邊的查詢列是一樣的話,那麼用union則反倒和用or的執行速度差很多,雖然這裏union掃描的是索引,而or掃描的是全表。
 


select gid,fariqi,neibuyonghu,reader,title from Tgongwen


          where fariqi=''2004-9-16'' or fariqi=''2004-2-5''


用時:6423毫秒。掃描計數 2,邏輯讀 14726 次,物理讀 1 次,預讀 7176 次。


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


union


select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi=''2004-2-5''


用時:11640毫秒。掃描計數 8,邏輯讀 14806 次,物理讀 108 次,預讀 1144 次。

9、字段提取要按照“需多少、提多少”的原則,避免“select *”
  我們來做一個試驗:


select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc


用時:4673毫秒


select top 10000 gid,fariqi,title from tgongwen order by gid desc


用時:1376毫秒


select top 10000 gid,fariqi from tgongwen order by gid desc


用時:80毫秒

  由此看來,我們每少提取一個字段,數據的提取速度就會有相應的提升。提升的速度還要看您捨棄的字段的大小來判斷。

10、count(*)不比count(字段)慢
  某些資料上說:用*會統計所有列,顯然要比一個世界的列名效率低。這種說法其實是沒有根據的。我們來看:


select count(*) from Tgongwen


用時:1500毫秒


select count(gid) from Tgongwen


用時:1483毫秒


select count(fariqi) from Tgongwen


用時:3140毫秒


select count(title) from Tgongwen


用時:52050毫秒

  從以上可以看出,如果用count(*)和用count(主鍵)的速度是相當的,而count(*)卻比其他任何除主鍵以外的字段彙總速度要快,而且字段越長,彙總的速度就越慢。我想,如果用count(*), SQL SERVER可能會自動查找最小字段來彙總的。當然,如果您直接寫count(主鍵)將會來的更直接些。

11、order by按聚集索引列排序效率最高
  我們來看:(gid是主鍵,fariqi是聚合索引列):


select top 10000 gid,fariqi,reader,title from tgongwen


用時:196 毫秒。 掃描計數 1,邏輯讀 289 次,物理讀 1 次,預讀 1527 次。


select top 10000 gid,fariqi,reader,title from tgongwen order by gid asc


用時:4720毫秒。 掃描計數 1,邏輯讀 41956 次,物理讀 0 次,預讀 1287 次。


select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc


用時:4736毫秒。 掃描計數 1,邏輯讀 55350 次,物理讀 10 次,預讀 775 次。


select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi asc


用時:173毫秒。 掃描計數 1,邏輯讀 290 次,物理讀 0 次,預讀 0 次。


select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi desc


用時:156毫秒。 掃描計數 1,邏輯讀 289 次,物理讀 0 次,預讀 0 次。

  從以上我們可以看出,不排序的速度以及邏輯讀次數都是和“order by 聚集索引列” 的速度是相當的,但這些都比“order by 非聚集索引列”的查詢速度是快得多的。
  同時,按照某個字段進行排序的時候,無論是正序還是倒序,速度是基本相當的。

12、高效的TOP
  事實上,在查詢和提取超大容量的數據集時,影響數據庫響應時間的最大因素不是數據查找,而是物理的I/0操作。如:


select top 10 * from (


select top 10000 gid,fariqi,title from tgongwen


where neibuyonghu=''辦公室''


order by gid desc) as a


order by gid asc


  這條語句,從理論上講,整條語句的執行時間應該比子句的執行時間長,但事實相反。因爲,子句執行後返回的是10000條記錄,而整條語句僅返回10條語句,所以影響數據庫響應時間最大的因素是物理I/O操作。而限制物理I/O操作此處的最有效方法之一就是使用TOP關鍵詞了。TOP關鍵詞是SQL SERVER中經過系統優化過的一個用來提取前幾條或前幾個百分比數據的詞。經筆者在實踐中的應用,發現TOP確實很好用,效率也很高。但這個詞在另外一個大型數據庫ORACLE中卻沒有,這不能說不是一個遺憾,雖然在ORACLE中可以用其他方法(如:rownumber)來解決。在以後的關於“實現千萬級數據的分頁顯示存儲過程”的討論中,我們就將用到TOP這個關鍵詞。
  到此爲止,我們上面討論瞭如何實現從大容量的數據庫中快速地查詢出您所需要的數據方法。當然,我們介紹的這些方法都是“軟”方法,在實踐中,我們還要考慮各種“硬”因素,如:網絡性能、服務器的性能、操作系統的性能,甚至網卡、交換機等。

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