1. 程式人生 > >SQL Server索引設計 <第五篇>

SQL Server索引設計 <第五篇>

字段排序 暫停 最快 get include 對象 聚合函數 要花 可能性

  SQL Server索引的設計主要考慮因素如下:

  檢查WHERE條件和連接條件列;

  使用窄索引;

  檢查列的選擇性;

  檢查列的數據類型;

  考慮列順序;

  考慮索引類型(聚集索引OR非聚集索引);

一、檢查WHERE條件列和鏈接條件列

  當一個查詢提交到SQL Server時,查詢優化器嘗試為查詢中引用的所有表查找最佳的數據訪問機制。下面列出查詢優化器針對WHERE和連接的工作方式:

  1. 優化器識別WHERE子句和連接條件中包含的列。
  2. 接著優化器檢查這些列上的索引。
  3. 優化器通過從索引上維護的統計確定子句的選擇性,評估每個索引的有效性。
  4. 最終,優化器根據前面幾個步驟中手機的信息,估計讀取所限定行開銷最低的方法。

  為了理解WHERE子句在查詢中的重要性,來考慮一個示例。

  SELECT * FROM Person WHERE Id = 100;

  假設上面的表Id列為聚集索引。上面的語句有了WHERE子句,查詢優化器將檢查WHERE子句的列Id,確定Id列上有聚集索引,從聚集索引上的統計評估WHERE子句的高選擇性,並且決定使用這個索引。

  查詢優化器的表現說明,WHERE子句列幫助優化器選擇一個對查詢最優的索引操作。這也適用於兩個表之間的連接條件中使用的列。優化器查找到WHERE子句或連接條件列上的索引,如果可用,考慮使用該索引來從表中檢索行。查詢優化器在執行一個查詢時,考慮WHERE子句或連接條件列上的索引。因此WHERE子句或連接條件中頻繁使用的列上有索引將幫助優化器避免基本表的掃描。

  但是,當一個表中的數據總量非常小以至於可以放入一個數據頁,那麽表掃描可能比索引查找更快,如果有一個好的索引,但是仍然進行掃描,可以考慮這個問題。

二、使用窄索引

  可以在表中的一個列組合上創建索引,但是為了最好的性能,盡量在索引中使用較少的列。還應該避免在索引中使用寬數據類型的列。

  •   窄索引:索引中的列數盡可能少;
  •   寬數據類型:占用空間比較大的數據類型,如:CHAR、VARCHAR、NVARCHAR、CLOB等。除非絕對必須,否則在索引中要把大尺寸的寬數據類型的列的使用降到最少。

  窄索引可以在8KB的索引頁面上容納比寬索引更多的行,這將有如下優點:

  1. 減少I/O數量(讀取更少的8KB頁面);
  2. 使數據庫緩存更有效,因為SQL Server可以緩存更少的索引頁面,從而減少內存中的索引頁面所需的邏輯讀操作;
  3. 減少數據庫存儲空間;

  下面以後一個示例來說明窄索引的好處:

  第一次,我們的索引僅僅包含Name列:

  技術分享

  第二次,我們的索引INCLUDE多了兩列:

  技術分享

  我們看到。包含多了兩個列之後,邏輯讀取比一個列多?為什麽呢?因為包含多了兩個列,索引需要占用更大的空間,一個數據頁放的索引行少了,就需要讀取更多的數據頁。

三、索引列的選擇性

  索引,特別是非聚集索引,主要在索引中有相當高級別的選擇性的情況下是有益的。所謂選擇性,指的是列中唯一值的百分比。列中的唯一值百分比越高,選擇性就越高,從而索引的溢出就越大。如果一個表中有2000條記錄,表索引列有1990個不同的值,那麽這個索引的選擇性就是1980/2000=0.99。

  在前面的學習中已經了解到,在非聚集索引中的查詢實際上只是開始。要找到真正的數據,仍需要對聚集索引再執行一次循環遍歷。甚至使用堆上的非聚集索引,仍然需要執行多個單獨的物理讀操作。

  如果在非聚集索引中的一個查找將要在聚集索引上產生多個額外的查找,那麽進行表掃描可能更好。這裏可能產生的影響實際上是非常驚人的。如果被索引的列唯一性達不到90%~95%,那麽考慮由非聚集索引創建的循環過程是不值得的。比如一個性別選項,設置為了bit,然後建索引。查詢優化器是不會考慮使用這種索引的。

  由以上的分析,可知主鍵的選擇性是100%,選擇性越接近主鍵,建在該列的索引的效率就會越高。

  索引的可選擇性是衡量索引的利用率的方法,比如在極端的情況下,一個表記錄數是1000,而索引列的值只有5個不同的值,則索引的可選擇性很差(只有0.005)。這樣的情形使用全表掃描要比采用索引還好。

  下面來實操下,計算下索引的選擇性,當然測試表數據小,可能查詢時即使有了索引,SQLServer也未必會用。

  技術分享

  由以上信息可計算得到對fdkeyname列的索引選擇性為

  110/119 = 0.924

  這樣還是麻煩啊?難道手工算嗎?下面給出一條SQL語句查出選擇性的方法:

SELECT CAST(count(DISTINCT fdkeyname) AS FLOAT) / CAST(count(*) AS FLOAT)
FROM JM_Keyword;

  技術分享

  選擇性規則的一個例外與外鍵有關,如果表中有一列是外鍵,那麽在該列上有一個索引,這很可能是有益的。為什麽是外鍵而不是其他列呢?外鍵常常是與它們引用的表連接的目標。不管選擇性如何,索引在連接性能方面是非常有幫助的,因為它們允許合並連接。合並連接從每個表中獲取一行進行比較,查看它們是否和連接條件匹配。因為兩個表中的相關列上都存在索引,所以對兩個行的查找是非常快的。

  下面以一個示例來說明問題:

  我在一個Person表中的性別列建立了一個索引,然後來查看查詢優化器的查詢方式:

  技術分享

技術分享

  為什麽查詢優化器不選擇從Gender列的索引來查找數據呢?

  我要返回前10條性別為"男"數據,如果使用索引,我們知道這個Gender列上的索引的選擇性大約為50%。SQL Server即使通過索引找到了前10條性別為男的聚集列,也還要再通過Id到聚集索引中去查找數據,這樣還不如直接掃描聚集表快。因此SQL Server的查詢優化器忽略了這個索引。

  通過WITH INDEX(索引名)可以強制使用索引查找,下面給出這兩種查詢方式的讀取次數比較:

  強制索引方式讀取:

  技術分享

  查詢優化器選擇讀取:

  技術分享

  由上面我們可以看到,強制使用索引的話,邏輯讀取高,但是預讀少。我們知道預讀是與分析並行執行,而且能夠載入緩存中的。從SQL Server的選擇來看,基本上可以得出一個結論,邏輯讀比預讀更加占用時間。

四、檢查索引的數據類型

  索引列的數據類型也是很重要的。例如,在一個整數鍵值上的索引查詢是非常快的,這是因為int數據類型的尺寸很小,而且算數操縱很容易。也可以使用int數據的其他變種(bigint,smallint,tinyint)作為索引列,而字符串數據類型(char、varchar、nchar、ncarchar、)需要字符串匹配操作,這通常比整數匹配操作的開銷更大。

  假設希望在一列上創建索引但卻有兩個候選列,一個是int數據類型,一個是char(4)數據類型。這兩種數據類型在SQL Server 2008中大小都是4字節,但是仍然應該首選int數據類型作為索引。因為char(4)數據類型中的值1實際上保存為1後面跟著3個空格,4個字節組合是0x35、0x20、0x20、0x20。CPU不理解如何在這個數據上執行算數運算。因此在算數操作之前要將其轉換為一個整數,而在int數據類型中,值1被保存為0x00000001。CPU可以簡單地在這個數據上執行算數運算。

五、索引列順序

  索引鍵值在索引的第一列上排序,然後再一次再下一列中排序。

  假設我們的在一張表中建立一個復合索引:

  CREATE NONCLUSTERED INDEX indexName ON Table(c1,c2) 

  那麽索引中的數據大概如下:

c1 c2
1 1
1 2
2 1
2 2
3 1
3 2
  假設大部分在上表上的查詢與下面的語句類似
  SELECT * FROM Table WHERE c1 = 12
  SELECT * FROM Table WHERE c2 = 12 AND c1 = 12

  (c2,c1)對上面兩個查詢都有利,但是(c1,c2)上的索引就不合適,因為它首先在c1上排序,而第一個SQL語句需要在c2上排序。

  這就好比使用電話本。所有項都是按先姓後名的方式進行索引-如果值知道要通電話的人的名是“備”,那麽這種排列順序不能帶來什麽好處。另一方面,如果只知道他的姓是“劉”,那麽索引將可以用來縮小查找範圍。

六、考慮索引類型

  考慮索引的類型 SQL Server中有兩種主要的索引類型:聚集索引和非聚集索引。這兩種類型都為B-樹結構。兩者之間的主要區別是聚集索引中的葉子頁是表的數據。因此表中的數據和聚集索引的順序相同,這意味著,聚集索引就是該表。在決定使用索引類型時,兩種索引類型的葉子級別上的差別變得非常重要。

  一個表只有一個聚集索引,應該明智地選擇它。

  SQLServer在默認情況下,主鍵和聚集索引是一起創建的。如果不想將主鍵聲明為聚集索引,那麽在創建表時,只需添加NONCLUSTERED關鍵字。

CREATE TABLE MyTableKeyExample
{
  Column1 int IDENTITY
    PRIMARY KEY NONCLUSTERED,
  Column2 int 
}

  一旦創建了索引,改變它的唯一方法是刪除和重建它,所以需要一開始就做對。

  如果改變了聚集索引所在的列,那麽SQL Server將需要對整個表完全重新排序(因為對於聚集索引,表的排列順序和索引順序是相同的)。

  對於數據比較多的表,改變聚集索引,需要重新排序的數據非常多,要從以下幾個方面進行考慮。

  它將需要花費多長時間。
  是否有足夠的空間?為了在聚集索引上執行重新排序,額外需要的平均空間量將為表已經占用空間量的1.2倍。確保有足夠的空間來操作。
  應當使用SORT_IN_TEMPDB選項嗎?如果tempdb位於與主數據庫不同的物理陣列上,並且它有足夠的空間,那麽答案是肯定的。

  1、正面觀點

  如果列常作為範圍查詢的對象,那麽聚集索引對這類查詢是很有用的。這類查詢通常使用between語句或<or>符號。使用GROUP BY以及利用MAX、MIN和COUNT聚合函數的查詢也是使用範圍和偏好聚集索引的查詢的重要示例。聚集索引適合用於此處,是因為搜索可以直接到達物理數據中的特定點,可一直讀數據,直到範圍的末端,然後停止。這種方法非常有效。當想要數據基於聚集鍵排序(ORDER BY),聚集也是極好的方法。

  2、反面觀點

  有兩種情況下,你可能不想創建聚集索引。

  (1)、當有更好的位置來使用它時。不要因為列看上去適合做聚集索引就將它用作聚集索引(主鍵是最常見的罪魁禍首)-要確定沒有更合適的其他列。

  (2)、在將要以非連續的順序進行大量插入時。這會進行頁拆分,並且會消耗大量時間。

  例如,一個交易系統,用

  ARXXXX

  GLXXXX

  APXXXX

  作為主鍵,並使用默認的聚集索引,那麽在插入數據的時候,經常會發生頁拆分。因為數據會按照聚集索引進行排序,那麽不停的錄入數據,就可能會經常性地發生頁拆分,引起短暫停頓。

  幸運的是,有一些方法可以避免以上情形:

  選擇在插入時是連續的聚集鍵。可以以此創建一個標識列,或者也可以使用另一個列,該列對於任何輸入交易來說,在邏輯上都是連續的。

  選擇不在這個表上使用聚集索引。對於類似的這裏的情形來說,這通常是最好的選擇,因為在對上非聚集索引中的插入一般比在聚集鍵上的插入更快。  

  何時應該使用聚集索引與非聚集索引

動作描述        使用聚集索引      使用非聚集索引
列經常被分組排序    應           應
返回某範圍內的數據   應           不應
一個或極少不同值    不應          不應
小數目的不同值     應           不應
大數目的不同值    不應          應
頻繁更新的列      不應          應
外鍵列         應           應
主鍵列         應           應
頻繁修改索引列     不應          應

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

  3、結合實際,談索引使用的誤區

下面列出在實踐中的一些誤區:

1、主鍵就是索引

  這種想法是極端錯誤的,是對聚集索引的一種浪費。雖然SQL SERVER默認是在竹簡上簡歷聚集索引的。通常,我們會在每個表都建立一個Id列,以區分每條數據,並且這個Id列是自動增大的,增長量一半設為1。以一個辦公自動化的紫銅為例子。如果將Id列設為主鍵,SQL SERVER會將此列默認為聚集索引,這樣做有好處,就是可以讓你的數據在數據庫中按照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列上,就像以上的第1、2種情況,在網頁上的表現就是超時,根本就無法顯示。這也是我摒棄ID列作為聚集索引的一個最重要的因素。

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

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

2、只要建立索引就能顯著提高查詢速度

  事實上,我們可以發現上面的例子中,第2、3條語句完全相同,且建立索引的字段也相同;不同的僅是前者在fariqi字段上建立的是非聚合索引,後者在此字段上建立的是聚合索引,但查詢速度卻有著天壤之別。所以,並非是在任何字段上簡單地建立索引就能提高查詢速度。從建表的語句中,我們可以看到這個有著1000萬數據的表中fariqi字段有5003個不同記錄。在此字段上建立聚合索引是再合適不過了。在現實中,我們每天都會發幾個文件,這幾個文件的發文日期就相同,這完全符合建立聚集索引要求的:“既不能絕大多數都相同,又不能只有極少數相同”的規則。由此看來,我們建立“適當”的聚合索引對於我們提高查詢速度是非常重要的。 事實上,我們可以發現上面的例子中,第2、3條語句完全相同,且建立索引的字段也相同;不同的僅是前者在fariqi字段上建立的是非聚合索引,後者在此字段上建立的是聚合索引,但查詢速度卻有著天壤之別。所以,並非是在任何字段上簡單地建立索引就能提高查詢速度。從建表的語句中,我們可以看到這個有著1000萬數據的表中fariqi字段有5003個不同記錄。在此字段上建立聚合索引是再合適不過了。在現實中,我們每天都會發幾個文件,這幾個文件的發文日期就相同,這完全符合建立聚集索引要求的:“既不能絕大多數都相同,又不能只有極少數相同”的規則。由此看來,我們建立“適當”的聚合索引對於我們提高查詢速度是非常重要的。

3、把所有需要提高查詢速度的字段都加進聚集索引,以提高查詢速度

  上面已經談到:在進行數據查詢時都離不開字段的是“日期”還有用戶本身的“用戶名”。既然這兩個字段都是如此的重要,我們可以把他們合並起來,建立一個復合索引(compound index)。
  很多人認為只要把任何字段加進聚集索引,就能提高查詢速度,也有人感到迷惑:如果把復合的聚集索引字段分開查詢,那麽查詢速度會減慢嗎?帶著這個問題,我們來看一下以下的查詢速度(結果集都是25萬條數據):(日期列fariqi首先排在復合聚集索引的起始列,用戶名neibuyonghu排在後列)

1select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>2004-5-5 

  查詢速度:2513毫秒

2select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>2004-5-5 and neibuyonghu=辦公室 

  查詢速度:2516毫秒

3select gid,fariqi,neibuyonghu,title from Tgongwen where neibuyonghu=辦公室

  查詢速度:60280毫秒

  從以上試驗中,我們可以看到如果僅用聚集索引的起始列作為查詢條件和同時用到復合聚集索引的全部列的查詢速度是幾乎一樣的,甚至比用上全部的復合索引列還要略快(在查詢結果集數目一樣的情況下);而如果僅用復合聚集索引的非起始列作為查詢條件的話,這個索引是不起任何作用的。當然,語句1、2的查詢速度一樣是因為查詢的條目數一樣,如果復合索引的所有列都用上,而且查詢結果少的話,這樣就會形成“索引覆蓋”,因而性能可以達到最優。同時,請記住:無論您是否經常使用聚合索引的其他列,但其前導列一定要是使用最頻繁的列。


其他書上沒有的索引使用經驗總結
1、用聚合索引比用不是聚合索引的主鍵速度快
下面是實例語句:(都是提取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。

2、用聚合索引列比用一般的主鍵作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萬以上,則二者的速度差別不明顯。

3、使用聚合索引列內的時間段,搜索時間會按數據占整個數據表的百分比成比例減少,而無論聚合索引使用了多少個

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毫秒
5、其他註意事項
  “水可載舟,亦可覆舟”,索引也一樣。索引有助於提高檢索性能,但過多或不當的索引也會導致系統低效。因為用戶在表中每加進一個索引,數據庫就要做更多的工作。過多的索引甚至會導致索引碎片。
  所以說,我們要建立一個“適當”的索引體系,特別是對聚合索引的創建,更應精益求精,以使您的數據庫能得到高性能的發揮。
  當然,在實踐中,作為一個盡職的數據庫管理員,您還要多測試一些方案,找出哪種方案效率最高、最為有效。

六、聚集索引的重要性和如何選擇聚集索引

  在上一節的標題中,筆者寫的是:實現小數據量和海量數據的通用分頁顯示存儲過程。這是因為在將本存儲過程應用於“辦公自動化”系統的實踐中時,筆者發現這第三種存儲過程在小數據量的情況下,有如下現象:

1、分頁速度一般維持在1秒和3秒之間。

2、在查詢最後一頁時,速度一般為5秒至8秒,哪怕分頁總數只有3頁或30萬頁。
雖然在超大容量情況下,這個分頁的實現過程是很快的,但在分前幾頁時,這個1-3秒的速度比起第一種甚至沒有經過優化的分頁方法速度還要慢,借用戶的話說就是“還沒有ACCESS數據庫速度快”,這個認識足以導致用戶放棄使用您開發的系統。

  筆者就此分析了一下,原來產生這種現象的癥結是如此的簡單,但又如此的重要:排序的字段不是聚集索引!
  本篇文章的題目是:“查詢優化及分頁算法方案”。筆者只所以把“查詢優化”和“分頁算法”這兩個聯系不是很大的論題放在一起,就是因為二者都需要一個非常重要的東西――聚集索引。
在前面的討論中我們已經提到了,聚集索引有兩個最大的優勢:
  1、以最快的速度縮小查詢範圍。
  2、以最快的速度進行字段排序。

第1條多用在查詢優化時,而第2條多用在進行分頁時的數據排序。

  而聚集索引在每個表內又只能建立一個,這使得聚集索引顯得更加的重要。聚集索引的挑選可以說是實現“查詢優化”和“高效分頁”的最關鍵因素。
  但要既使聚集索引列既符合查詢列的需要,又符合排序列的需要,這通常是一個矛盾。
  筆者前面“索引”的討論中,將fariqi,即用戶發文日期作為了聚集索引的起始列,日期的精確度為“日”。這種作法的優點,前面已經提到了,在進行劃時間段的快速查詢中,比用ID主鍵列有很大的優勢。

但在分頁時,由於這個聚集索引列存在著重復記錄,所以無法使用max或min來最為分頁的參照物,進而無法實現更為高效的排序。而如果將ID主鍵列作為聚集索引,那麽聚集索引除了用以排序之外,沒有任何用處,實際上是浪費了聚集索引這個寶貴的資源。

  為解決這個矛盾,筆者後來又添加了一個日期列,其默認值為getdate()。用戶在寫入記錄時,這個列自動寫入當時的時間,時間精確到毫秒。即使這樣,為了避免可能性很小的重合,還要在此列上創建UNIQUE約束。將此日期列作為聚集索引列。

  有了這個時間型聚集索引列之後,用戶就既可以用這個列查找用戶在插入數據時的某個時間段的查詢,又可以作為唯一列來實現max或min,成為分頁算法的參照物。
  經過這樣的優化,筆者發現,無論是大數據量的情況下還是小數據量的情況下,分頁速度一般都是幾十毫秒,甚至0毫秒。而用日期段縮小範圍的查詢速度比原來也沒有任何遲鈍。
聚集索引是如此的重要和珍貴,所以筆者總結了一下,一定要將聚集索引建立在:
  1、您最頻繁使用的、用以縮小查詢範圍的字段上;
  2、您最頻繁使用的、需要排序的字段上。

SQL Server索引設計 <第五篇>