SQL Server創建索引時,可以指定Unique使之成為唯一索引?!拔ㄒ弧鳖櫭剂x,但是兩都到底有什么區別呢?因為索引也是一種物理結構,所以還是要從存儲和結構上分析。
索引結構分葉級和非葉級,分析時我們要分開來看,這個很重要。
文中涉及的索引行大小計算,參考MSDN估計數據庫大小索引部分。
1. 非唯一聚集索引和唯一聚集索引
創建兩個測試表,各10000條整數,tb1唯一,tb2非唯一,有1000條為9999的重復值。
Codecreate table tb1(col1 int);declare @i int=1while @i<10001begin insert into tb1 values(@i); set @i=@i+1; end;create unique clustered index ucix on tb1 (col1)go-------create table tb2(col2 int);declare @i int=1while @i<9001begin insert into tb2 values(@i); set @i=@i+1; end;goinsert into tb2 values(9999)go 1000;create clustered index cix on tb2 (col2)go
先查詢索引的一些基本狀況:
從上面的結果可以看到,無論是葉級還是非葉級,非唯一聚集索引的索引行都比唯一的大一些,所以所占頁也多一點。當然,因為測試數據很小,又是int,所以不明顯。
那到底大在哪里呢?將兩者的非葉級頁和葉級頁放在一起比一下就知道了。先找出頁號,再用DBCC PAGE來查看。
通過Paul S. Randal寫的存儲過程sp_allocationMetadata可以查到根頁和每級索引的首頁。
就挑這兩個頁做對比。
發現多出一個UNIQUIFIER,同樣葉級也是一樣。MSDN說明:
“如果聚集索引不是唯一的索引,SQL Server 將添加在內部生成的值(稱為唯一值)以使所有重復鍵唯一。此四字節的值對于用戶不可見。僅當需要使聚集鍵唯一以用于非聚集索引中時,才添加該值。”
還有UNIQUIFIER不是一個全局自增列,重復記錄增加時此值會發生改變,并且它是一個可為null的變長列。
現在來算一算索引行大?。?/p>
兩個表都是只有一個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.堆表上的唯一和非唯一的非聚集索引
Codecreate table IndexTest(id int identity, UniqueCol int, NonuniqueCol int) go set nocount on; declare @i int=1; while @i<100000 begin insert into IndexTest values(@i,@i); set @i=@i+1; end set nocount off; go create unique index UIX_UniqueCol on IndexTest (UniqueCol); create index IX_NonuniqueCol on IndexTest (NonuniqueCol);go select i.name,ips.index_id,ips.index_type_desc,index_depth,index_level,page_count,record_count,min_record_size_in_bytes as minSize,max_record_size_in_bytes as maxSize,avg_record_size_in_bytes as avgSizefrom sys.dm_db_index_physical_stats(DB_ID('test'),OBJECT_ID('IndexTest'),null,null,'DETAILED') ipsinner join sys.indexes ion ips.object_id=i.object_id and ips.index_id=i.index_idorder by name,index_level
兩者的頁級大小是一樣的,非葉級頁的相差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 alter table IndexTest add constraint PK_IndexTest PRimary key clustered (ID)
這里有意思的是聚集索引的非葉級行只有11字節,跟同樣的1.中的14相差了3字節。這3字節是因為現在這個表,索引列是自增主鍵,是不能為NULL的,所以就沒有NULL位圖的3個字節的開銷了。
兩者的葉級行大小一樣,看一下長的是不是一樣:
兩者的區別在于對聚集索引鍵的引用上,即“id”和“id(key)”。MSDN說明:
“如果非聚集索引不是唯一的,數據行定位符將與非聚集索引鍵組合使用,以便為每一行生成唯一的鍵值。如果非聚集索引在聚集索引之上,則數據行定位符是聚集鍵。”
唯一非聚集索引中的“id
新聞熱點
疑難解答