SQL Server 深刻解析索引存儲(非彙集索引)

 標籤:SQL SERVER/MSSQL SERVER/數據庫/DBA/索引體系結構/非彙集索引html

概述  

非彙集索引與彙集索引具備相同的 B 樹結構,它們之間的顯著差異在於如下兩點:數據庫

  • 基礎表的數據行不按非彙集鍵的順序排序和存儲。

  • 非彙集索引的葉層是由索引頁而不是由數據頁組成。

既可使用匯集索引來爲表或視圖定義非彙集索引,也能夠根據堆來定義非彙集索引。非彙集索引中的每一個索引行都包含非彙集鍵值和行定位符。此定位符指向彙集索引或堆中包含該鍵值的數據行。session

非彙集索引行中的行定位器或是指向行的指針,或是行的彙集索引鍵,以下所述:測試

  • 若是表是堆(意味着該表沒有彙集索引),則行定位器是指向行的指針。該指針由文件標識符 (ID)、頁碼和頁上的行數生成。整個指針稱爲行 ID (RID)。

  • 若是表有彙集索引或索引視圖上有彙集索引,則行定位器是行的彙集索引鍵。若是彙集索引不是惟一的索引,SQL Server 將添加在內部生成的值(稱爲惟一值)以使全部重複鍵惟一。此四字節的值對於用戶不可見。僅當須要使彙集鍵惟一以用於非彙集索引中時,才添加該值。SQL Server 經過使用存儲在非彙集索引的葉行內的彙集索引鍵搜索彙集索引來檢索數據行。

對於索引使用的每一個分區,非彙集索引在 index_id >0 的 sys.partitions 中都有對應的一行。默認狀況下,一個非彙集索引有單個分區。若是一個非彙集索引有多個分區,則每一個分區都有一個包含該特定分區的索引行的 B 樹結構。例如,若是一個非彙集索引有四個分區,那麼就有四個 B 樹結構,每一個分區中一個。spa

根據非彙集索引中數據類型的不一樣,每一個非彙集索引結構會有一個或多個分配單元,在其中存儲和管理特定分區的數據。每一個非彙集索引至少有一個針對每一個分區的 IN_ROW_DATA 分配單元(存儲索引 B 樹頁)。若是非彙集索引包含大型對象 (LOB) 列,則還有一個針對每一個分區的 LOB_DATA 分配單元。此外,若是非彙集索引包含的可變長度列超過 8,060 字節行大小限制,則還有一個針對每一個分區的 ROW_OVERFLOW_DATA 分配單元。有關分配單元的詳細信息,請參閱表組織和索引組織。B 樹的頁集合由 sys.system_internals_allocation_units 系統視圖中的 root_page 指針定位。3d

 要很好的理解這篇文章的內容以前須要先閱讀我前面寫的上中部分的兩篇文章:指針

SQL Server 深刻解析索引存儲(堆)code

SQL Server 深刻解析索引存儲(彙集索引)htm

正文

非彙集索引結構

 

生成測試數據對象

 

CREATE TABLE Torder
(ID INT IDENTITY(1,1) NOT NULL,
NAME CHAR(100) NOT NULL,
pro VARCHAR(8000) NULL,
Statu INT NOT NULL,
IDATE DATETIME DEFAULT(GETDATE())
)
GO
---插入1000條測試數據
DECLARE @ID INT=1
WHILE(@ID<=1000)
BEGIN
INSERT INTO Torder(NAME,pro,Statu)VALUES('商品'+CONVERT(CHAR(20),@ID),REPLICATE(1,8000),LEFT(@ID,1))
SET @ID=@ID+1 
END
GO
---建立非彙集索引
CREATE INDEX IX_Torder ON Torder
(NAME,Statu
)
INCLUDE(IDATE)


SELECT DISTINCT so.name, so.object_id,sp.index_id,internals.type_desc,internals.total_pages, internals.used_pages, internals.data_pages,first_iam_page,  first_page, root_page
FROM sys.objects so
INNER JOIN sys.partitions sp ON so.object_id = sp.object_id
INNER JOIN sys.allocation_units sa ON sa.container_id = sp.hobt_id
INNER JOIN sys.system_internals_allocation_units internals ON internals.container_id = sa.container_id
WHERE so.object_id = object_id('Torder') 

 

因爲建立的表只有非彙集索引,因此整個表的頁存儲中有三部分數據:堆頁面、溢出頁面、索引頁面;

堆中共有20個數據頁和一個IAM頁;

溢出單元有1001個頁面包括一個IAM頁;

索引中共有20個頁其中18個數據頁一個ROOT頁和一個IAM頁.

一個堆頁對應多個溢出頁,由於Pro有8000個字節因此一行佔一頁,而表的其它字段只有116個字節一個堆頁能夠存50條記錄,因此並非一個溢出頁就惟一對應一個堆頁

 分析頁的存儲信息

---開啓跟蹤標誌
DBCC TRACEON(3604,2588)
--DBCC TRACEOFF(3604,2588)
---獲取對象的數據頁,結構:數據庫、對象、顯示
DBCC IND(Ixdata,Torder,-1)

上一章中已經講過了堆頁面和溢出頁面,因此如今就講非彙集索引頁

 看過前面的文章應該一眼就能看出1281頁是ROOT頁,如今就分析1281頁

分析非彙集索引根頁

DBCC page(Ixdata,1,1281,3)

如今來分析行定位指針是怎樣的:0x6801000001002F00

除去開頭的16進制標示,剩下總共8個字節,從右往左其中行號2個字節,文件標示ID2個字節,剩下的4個字節就是頁號了,因此

行號(002f)=47

文件頁(0001)=1

頁號(00000168)=360頁

如今查看360頁的信息

DBCC page(Ixdata,1,360,3)

47行的記錄正好是「商品150」

 分析非彙集索引索引頁

 

經過對比會發現索引頁比根頁多出了索引包含列值和鍵的哈希值,這個裏面的keyhashvalue應該是NAME和statu字段的值經過某種方法算出來的,這裏只是猜想經過平時的查詢你會產生這樣的猜想。

測試簡單的查詢

這裏的'商品150'和'商品153'都是1280頁中的記錄,1280頁是索引頁,其中'商品150'是該頁的第一條記錄
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN  
SELECT [ID]
      ,[NAME]
      ,[pro]
      ,[Statu]
      ,[IDATE]
  FROM [Ixdata].[dbo].[Torder]
  WHERE NAME='商品153'

--COMMIT 

另開一個窗口
SELECT
[request_session_id],
c.[program_name],
DB_NAME(c.[dbid]) AS dbname,
[resource_type],
[request_status],
[request_mode],
[resource_description],OBJECT_NAME(p.[object_id]) AS objectname,
p.[index_id]
FROM sys.[dm_tran_locks] AS a LEFT JOIN sys.[partitions] AS p
ON a.[resource_associated_entity_id]=p.[hobt_id]
LEFT JOIN sys.[sysprocesses] AS c ON a.[request_session_id]=c.[spid]
 WHERE c.[dbid]=DB_ID('Ixdata') AND a.[request_session_id]=58  ----要查詢申請鎖的數據庫
ORDER BY [request_session_id],[resource_type]

 

 

從上面的查詢過程能夠知道頁面總共讀取了三次(索引葉一次堆頁一次溢出頁一次)。

 

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
GO        
BEGIN TRAN  
 SELECT 
       [ID]
      ,[NAME]
      ,[pro]
      ,[Statu]
      ,[IDATE]
  FROM [Ixdata].[dbo].[Torder]
  WHERE NAME='商品150'                                                                                     
  

 

 

經過對比查詢'商品150'和'商品153'能夠看到若是查找的頁面的第一條記錄,它須要再讀取該索引頁的前一個頁面,若是該索引頁是第一頁就無需再讀除自己的其餘索引頁了,文章寫到後面反過來思考才知道爲何非彙集索引還須要多查找一個頁面了。由於非彙集索引是容許存在重複值因此才須要再往前查找,若是前面一個頁查找不到則結束,若是前面一個頁還沒查完會再往前一個頁進行查,固然查詢商品153的時候就已經判斷了前一條記錄的鍵值是不同的不然也是要再查詢前一個頁,這也是非彙集索引的一個特殊狀況吧!

索引掃描

update Torder
set statu=100
where id=1

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
GO        
BEGIN TRAN  
 SELECT 
       [ID]
      ,[NAME]
      ,[pro]
      ,[Statu]
      ,[IDATE]
  FROM [Ixdata].[dbo].[Torder]
  WHERE [Statu]=100      

該查詢總共掃描了18個索引頁+1個堆頁+1個溢出頁.

建立彙集索引

 

ALTER TABLE dbo.Torder ADD CONSTRAINT
    PK_Torder PRIMARY KEY CLUSTERED 
    (
    ID
    ) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]


SELECT DISTINCT so.name, so.object_id,sp.index_id,internals.type_desc,internals.total_pages, internals.used_pages, internals.data_pages,first_iam_page,  first_page, root_page
FROM sys.objects so
INNER JOIN sys.partitions sp ON so.object_id = sp.object_id
INNER JOIN sys.allocation_units sa ON sa.container_id = sp.hobt_id
INNER JOIN sys.system_internals_allocation_units internals ON internals.container_id = sa.container_id
WHERE so.object_id = object_id('Torder') 

非彙集索引數據頁比以前少了一頁

因爲如今的指針比以前的16進制指針要所佔有的字節要少,因此只須要17個頁面就能夠存下。

分析索引頁148

 

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN  
SELECT [ID]
      ,[NAME]
      ,[pro]
      ,[Statu]
      ,[IDATE]
  FROM [Ixdata].[dbo].[Torder]
  WHERE NAME='商品152'


在另外一個窗口打開
SELECT
[request_session_id],
c.[program_name],
DB_NAME(c.[dbid]) AS dbname,
[resource_type],
[request_status],
[request_mode],
[resource_description],OBJECT_NAME(p.[object_id]) AS objectname,
p.[index_id]
FROM sys.[dm_tran_locks] AS a LEFT JOIN sys.[partitions] AS p
ON a.[resource_associated_entity_id]=p.[hobt_id]
LEFT JOIN sys.[sysprocesses] AS c ON a.[request_session_id]=c.[spid]
 WHERE c.[dbid]=DB_ID('Ixdata') AND a.[request_session_id]=58  ----要查詢申請鎖的數據庫
ORDER BY [request_session_id],[resource_type]

從上面的邏輯讀取和查詢步驟能夠證明前面的猜想,應該是隱藏了一張行定位表。

若是表有彙集索引或索引視圖上有彙集索引,則行定位器是行的彙集索引鍵。若是彙集索引不是惟一的索引,SQL Server 將添加在內部生成的值(稱爲惟一值)以使全部重複鍵惟一。此四字節的值對於用戶不可見。僅當須要使彙集鍵惟一以用於非彙集索引中時,才添加該值。SQL Server 經過使用存儲在非彙集索引的葉行內的彙集索引鍵搜索彙集索引來檢索數據行。

查看索引統計信息

DBCC SHOW_STATISTICS ("dbo.Torder", IX_Torder);

 

前面建的包含索引有這三種組合方式,因此組合索引的第二個字段不被用來單獨作查找。

總結

  非彙集索引和彙集索引不同,彙集索引索引頁的鍵值指向數據頁的具體行;而非彙集索引不存在數據頁,非彙集索引的索引頁中的記錄行指向彙集索引或者堆的具體數據頁的數據行而後來獲取記錄,若是堆或彙集索引還存在溢出的話,從堆或者彙集索引的數據記錄還有指向溢出頁面的指針。

補充一下在非彙集索引中存在彙集索引與堆的優勢,看完上文你會發現非彙集索引的數據頁記錄的行定位指針分別指向彙集索引或堆的行,可是指向彙集索引的行定位是邏輯值而指向堆的是實際的rid值,邏輯值的好處就是在彙集索引起生分頁的狀況下,邏輯值不用改變也就無需更新非彙集索引的指針。

花了四天時間終於把這個系列的寫完了,從新去理解一遍把之前的一些不理解的知識點給弄明白了,仍是收穫不少。

 

若是文章對你們有幫助,但願你們能給個推薦,謝謝!!!

 

備註:

    做者:pursuer.chen

    博客:http://www.cnblogs.com/chenmh

本站點全部隨筆都是原創,歡迎你們轉載;但轉載時必須註明文章來源,且在文章開頭明顯處給明連接,不然保留追究責任的權利。

《歡迎交流討論》

相關文章
相關標籤/搜索