SQL Server創建索引時,可以指定Unique使之成為唯一索引。“唯一”顧名思義,但是兩都到底有什么區別呢?因為索引也是一種物理結構,所以還是要從存儲和結構上分析。
索引結構分葉級和非葉級,分析時我們要分開來看,這個很重要。
文中涉及的索引行大小計算,參考MSDN估計數據庫大小索引部分。
1. 非唯一聚集索引和唯一聚集索引
創建兩個測試表,各10000條整數,tb1唯一,tb2非唯一,有1000條為9999的重復值。
Code
先查詢索引的一些基本狀況:
從上面的結果可以看到,無論是葉級還是非葉級,非唯一聚集索引的索引行都比唯一的大一些,所以所占頁也多一點。當然,因為測試數據很小,又是int,所以不明顯。
那到底大在哪里呢?將兩者的非葉級頁和葉級頁放在一起比一下就知道了。先找出頁號,再用DBCC PAGE來查看。
通過Paul S. Randal寫的存儲過程sp_allocationMetadata可以查到根頁和每級索引的首頁。
就挑這兩個頁做對比。
發現多出一個UNIQUIFIER,同樣葉級也是一樣。MSDN說明:
“如果聚集索引不是唯一的索引,SQL Server 將添加在內部生成的值(稱為唯一值)以使所有重復鍵唯一。此四字節的值對於用戶不可見。僅當需要使聚集鍵唯一以用於非聚集索引中時,才添加該值。”
還有UNIQUIFIER不是一個全局自增列,重復記錄增加時此值會發生改變,並且它是一個可為null的變長列。
現在來算一算索引行大小:
兩個表都是只有一個int型可為NULL的字段,而聚集索引葉級是存儲數據本身
葉級是一個4字節的INT列,無變長列,加上3字節的NULL位圖,再加上4字節的行頭開銷:兩個表的葉級minSize =4+0+3+4=11
非葉級是一個4字節的INT列,無變長列,加上3字節的NULL位圖,加上1字節的行頭開銷,再加6字節的子頁指針:兩個表的非葉級minSize=4+0+3+1+6=14
tb1的索引行大小是一致的minSize=maxSize,因為它是唯一的。tb2的索引行大小不一致,有大有小,大的索引行是因為:a)不唯一 b)UNIQUIFIER
唯一標識列增加了2+1*2+4=8字節開銷,tb2的min和max相差就是這8字節。
tb2的葉級maxSize=4+8+3+4=19
tb2的非葉級maxSize=4+8+3+1+6=22
小結:非唯一聚集索引為保證索引鍵值唯一性,會生成UNIQUIFIER與鍵列一起組成索引鍵值。同時無論在葉級還是非葉頁級,都比唯一索引占用更多存儲空間。
2.堆表上的唯一和非唯一的非聚集索引
Code
兩者的頁級大小是一樣的,非葉級頁的相差8bytes。
跟1.中的分析方法一樣,挑兩個非葉級頁出來對比一下,看相差在哪里。
非唯一索引行多了一個HEAP RID,MSDN說明:
“如果非聚集索引不是唯一的,數據行定位符將與非聚集索引鍵組合使用,以便為每一行生成唯一的鍵值。如果非聚集索引在堆上,則數據行定位符是堆 RID。其大小是 8 個字節。”
兩者葉級索引行大小=INT型4字節+無變長列+1字節行頭+3字節NULL位圖+8字節RID=4+0+1+3+8=16
唯一索引的非葉級行=INT型4字節+無變長列+1字節行頭+3字節NULL位圖+6字節子頁索引=14
非唯一索引的非葉級行=INT型4字節+無變長列+1字節行頭+3字節NULL位圖+6字節子頁索引+8字節的RID=22
小結:堆表上的非唯一索引在非葉級索引行上比唯一索引多出一列行定位符RID,而葉級是一樣的,都有RID列。所以非唯一要占用更多的空間。
3.唯一聚集索引表上的唯一和非唯一非聚集索引
跟2.中的測試數據一樣,只是把ID列改成聚集主鍵。執行:
Code
這里有意思的是聚集索引的非葉級行只有11字節,跟同樣的1.中的14相差了3字節。這3字節是因為現在這個表,索引列是自增主鍵,是不能為NULL的,所以就沒有NULL位圖的3個字節的開銷了。
兩者的區別在於對聚集索引鍵的引用上,即“id”和“id(key)”。MSDN說明:
“如果非聚集索引不是唯一的,數據行定位符將與非聚集索引鍵組合使用,以便為每一行生成唯一的鍵值。如果非聚集索引在聚集索引之上,則數據行定位符是聚集鍵。”
唯一非聚集索引中的“id”是做為行定位符引用的,非唯一非聚集索引中的“id(key)”,不僅是做為行定位符引用,並且還是此索引本身鍵列(“key”的含義)。
非葉級行相差4個字節,對比一下頁的內容就知道差在哪里:
從上圖看出非唯一索引比唯一的多了一列”id(key)”,這是前者引用了聚集索引鍵並做做自己的鍵列,而唯一索引不需要這樣。
小結:唯一聚集索引表上的非唯一非聚集索引與唯一非聚集索引,在葉級上大小是一樣的,在非葉級行多引用一列“聚集鍵”,所以前者占用的存儲空間也會更大一些。
4. 非唯一聚集索引表上的唯一和非唯一非聚集索引
測試數據中給制造了一些重復數據。
Code
由前文的分析可知上圖所有索引的minSize和maxSize相差8字節都是由UNIQUEIFIER產生。
兩個非聚集索引的葉級行一樣大,原因跟3. 中分析的一樣,只是兩者除了引用聚集鍵之外,還會引用跟聚集鍵結合使用的UNIQUEIFIER。
但是非葉級頁兩差有着較大的差異,查看頁面:
從頁面內容可以看出,非唯一非聚集比唯一多了兩列,其它這兩列可以看成一列,原因就是當聚集索引不唯一時,會生成UNIQUIFIER並結合,用以保證聚集鍵唯一。
小結:非唯一聚集索引表上的非唯一非聚集索引在葉級行上大小是一樣的,而在非葉級行上前者比后大,所以也占用更多存儲空間。
總結:
1. 在表和索引設計階段,如果可能,字段設定為不允許NULL,索引設定為唯一。這樣節約存儲空間並提高了IO效率。
2. 聚集索引鍵列應該盡量選用窄的字段,因為非聚集索引會引用其鍵列。如果聚集鍵過大則會使非聚集索引同時也占用更多存儲空間。
3. SQL Server在創建索引時,默認是創建非唯一的。所以在創建索引時,要認真考慮是否可以創建為唯一索引。
4. 文中用詞有些繞。