索引鍵的唯一性(3/4):唯一聚集索引上的唯一和非唯一非聚集索引


在上篇文章里,我討論了唯一和非唯一聚集索引的區別。我們已經知道,SQL Server內部使用4 bytes的uniquifier來保證非唯一聚集索引行唯一。今天我們來看下唯一聚集索引上,唯一和非唯一非聚集索引的區別。當我們在表上定義PRIMARY KEY約束時,SQL Server會為我們創建唯一聚集索引;另外我們可以通過CREATE UNIQUE CLUSTERED INDEX語句在表上創建唯一聚集索引。下面的代碼會創建customers表,然后在它上面創建唯一聚集索引,最后在表上創建唯一和非唯一非聚集索引。

 1 -- Create a table with 393 length + 7 bytes overhead = 400 bytes 
 2 -- Therefore 20 records can be stored on one page (8.096 / 400) = 20,24 
 3 CREATE TABLE Customers 
 4 ( 
 5     CustomerID INT NOT NULL, 
 6     CustomerName CHAR(100) NOT NULL, 
 7     CustomerAddress CHAR(100) NOT NULL, 
 8     Comments CHAR(189) NOT NULL 
 9 ) 
10 GO
11   
12 -- Create a unique clustered index on the previous created table 
13 CREATE UNIQUE CLUSTERED INDEX idx_Customers ON Customers(CustomerID) 
14 GO  
15 -- Insert 80.000 records 
16 DECLARE @i INT = 1 
17 WHILE (@i <= 80000) 
18 BEGIN 
19     INSERT INTO Customers VALUES 
20     ( 
21         @i, 
22         'CustomerName' + CAST(@i AS CHAR), 
23         'CustomerAddress' + CAST(@i AS CHAR), 
24         'Comments' + CAST(@i AS CHAR) 
25     )     
26     SET @i += 1 
27 END 
28 GO
29 
30 -- Create a unique non clustered index on the clustered table 
31 CREATE UNIQUE NONCLUSTERED INDEX idx_UniqueNCI_CustomerID 
32 ON Customers(CustomerName) 
33 GO  
34 
35 -- Create a non-unique non clustered index on the clustered table 
36 CREATE NONCLUSTERED INDEX idx_NonUniqueNCI_CustomerID 
37 ON Customers(CustomerName) 
38 GO

在2個非聚集索引創建后,我們可以使用DMV sys.dm_db_index_physical_stats來查看索引的相關信息。

 1 -- Retrieve physical information about the unique non-clustered index 
 2 SELECT * FROM sys.dm_db_index_physical_stats 
 3 ( 
 4     DB_ID('ALLOCATIONDB'), 
 5     OBJECT_ID('Customers'), 
 6     2, 
 7     NULL, 
 8     'DETAILED'
 9 ) 
10 GO
11 
12 -- Retrieve physical information about the non-unique non-clustered index 
13 SELECT * FROM sys.dm_db_index_physical_stats 
14 ( 
15     DB_ID('ALLOCATIONDB'), 
16     OBJECT_ID('Customers'), 
17     3, 
18     NULL, 
19     'DETAILED'
20 ) 
21 GO

我們可以看到,唯一非聚集索引的記錄長度是107 bytes,非唯一非聚集索引的記錄長度是111 bytes。因此這2個索引的內部存儲格式肯定不同。我們從唯一非聚集索引開始分析。

我們可以通過DBCC IND命令找出索引根頁,聚集索引的INDEX ID為1,非聚集索引的INDEX ID從2開始,依次遞增,這里應該是2和3。

1 TRUNCATE TABLE dbo.sp_table_pages
2 INSERT INTO dbo.sp_table_pages
3 EXEC('DBCC IND(ALLOCATIONDB, Customers, -1)') 
4 
5 SELECT * FROM dbo.sp_table_pages ORDER BY IndexLevel DESC

從這里我們可以看出,唯一非聚集索引的根頁是20834,非唯一非聚集索引的根頁是21890。

我們看下唯一非聚集索引的根頁內容:

1 DBCC PAGE(ALLOCATIONDB, 1, 20834, 3)
2 GO

從圖中我們可以看到,每條索引記錄包含非聚集鍵(這里是唯一的)——即CustomerName列。

我們換參數1再來看看根頁信息:

1 DBCC TRACEON(3604)
2 DBCC PAGE(ALLOCATIONDB, 1, 20834, 1)
3 GO

這里的107 bytes包含下列信息:

  • 1 byte: 狀態位
  • n bytes:非唯一聚集索引鍵——這里是CustomerName列,100 bytes
  • 4 bytes:頁ID(PageID)
  • 2 bytes:文件ID(FileID)

在唯一非聚集索引里,所有非葉子層的每條索引記錄都包含這107 bytes信息。因此,你的非聚集索引鍵大小會影響到每個索引頁可以存儲多少條索引記錄。這樣的話,這個例子的CHAR(100),並不是一個很好的索引鍵。

我們繼續往下看,索引葉子層的存儲情況:

1 DBCC PAGE(ALLOCATIONDB, 1, 20834, 3)--根層
2 GO
3 
4 DBCC PAGE(ALLOCATIONDB, 1, 20833, 3)--中間層
5 GO
6 
7 DBCC PAGE(ALLOCATIONDB, 1, 21098, 3)--葉子層
8 GO

從圖中我們可以看到,SQL Server在葉子層這里保存聚集鍵(即CustomerID列的值)。這個值是SQL Server用來指向聚集索引里對應記錄的指針。手上有了這個值,SQL Server就可以在聚集索引找到對應記錄——通過聚集索引查找(Clustered Index Seek)運算符。這和在堆表上定義的非聚集索引有重大區別。因為在堆表里,SQL Server使用葉子層的HEAP RID直接指向數據頁里存儲的對應記錄。因此,SQL Server不用訪問額外索引,就可以直接正確讀取到數據頁。

這也意味着SQL Server在堆表上通過非聚集索引找記錄,比在聚集表上通過非聚集索引找記錄快很多,因為SQL Server不需要執行額外的聚集索引查找(Clustered Index Seek)運算符。因此在堆表上可以讀取更少的頁正確找到記錄。當不要高估這個細節,想想在堆表通過使用非聚集索引,性能上可以有多少好處。事實上,SQL Server總是盡量把索引頁放在緩存區管理器里,因此對於SQL Server來說,使用額外的聚集索引查找(Clustered Index Seek)從聚集索引里找回記錄,成本更低。

現在我們來分析下非唯一非聚集索引。先來看看根頁:

1 DBCC PAGE(ALLOCATIONDB, 1, 21890, 3)
2 GO

從上圖可以看出,非唯一非聚集索引根頁里,SQL Server這里保存里非聚集索引鍵和聚集索引鍵,這個和剛才的唯一聚集索引根頁是不一樣的。

SQL Server這里需要使用唯一聚集鍵來使非唯一非聚集索引鍵唯一。這在非唯一非聚集索引的每一層都會保存,從索引根頁到葉子層。這就是說你需要更多的存儲空間來保存索引,因為SQL Server在每條索引記錄里不僅保存唯一聚集鍵,也保存非唯一非聚集索引鍵。因此當你選擇不好的聚集鍵(像 CHAR(100)等)時,情況會變得更糟。

1 DBCC PAGE(ALLOCATIONDB, 1, 21890, 3)--根層
2 GO
3 
4 DBCC PAGE(ALLOCATIONDB, 1, 21889, 3)--中間層
5 GO
6 
7 DBCC PAGE(ALLOCATIONDB, 1, 22087, 3)--葉子層
8 GO

我們換參數1再來看看根頁信息:

1 DBCC TRACEON(3604)
2 DBCC PAGE(ALLOCATIONDB, 1, 21890, 1)
3 GO

這111 bytes包括:

  • 1 byte:狀態位
  • n bytes:非唯一非聚集索引鍵——這里是CustomerName列,100 bytes
  • n bytes:唯一聚集索引鍵——這里是CustomerID列,4 bytes
  • 4 bytes:頁ID(PageID)
  • 2 bytes:文件ID(FileID)

當你把這些字節長度匯總后,你就得到了剛才提到的111 bytes。因此在你創建非唯一非聚集索引時,就要考慮到這些額外存儲,因為它會影響到你的非聚集索引的每一層。

在這個系列的下篇文章里,我們最后來看下在非唯一聚集索引上,唯一和非唯一非聚集索引的區別,請繼續關注!

參考文章:

https://www.sqlpassion.at/archive/2010/08/31/unique-and-non-unique-non-clustered-indexes-on-a-unique-clustered-index/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM