SQLSERVER聚集索引與非聚集索引的再次研究(上)

原文  http://www.cnblogs.com/lyhabc/p/3196479.html


上篇主要說聚集索引

由於本人還是SQLSERVER菜鳥一枚,加上一些實驗的邏輯嚴謹性,

單寫《SQLSERVER聚集索引與非聚集索引的再次研究(上)》就用了12個小時,兩篇文章加起來最起碼寫了20個小時,
本人非常非常用心的努力完成這兩篇文章,希望各位看官給點意見o(∩_∩)o

爲了搞清楚索引內部工作原理和結構,真是千頭萬緒,這篇文章只是作爲參考,裏面的觀點不一定正確

有一些問題,msdn裏,網上的文章裏,博客園裏都有提到,但是這些問題的答案是正確的嗎?其實有時候我自己都想知道答案

比如,畫聚集索引的圖,有一些人用表格來表示,但是他們正確嗎?

以前知道聚集索引 非聚集索引是B樹 二叉樹結構,又知道執行計劃圖標很像二叉樹很傳神,但是還是覺得很抽象

這篇文章寫完以後還是比較抽象但是最起碼比以前清晰一些了

 

有很多問題不知道爲什麼,但是MSDN就是這樣說的,既然說得這麼模糊不如自己做一下實驗,驗證一下MSDN的內容吧o(∩_∩)o

-------------------------------------------- 華麗的分割線 ---------------------------------------------

 先來看一下索引的結構,文章裏面的一些結構圖都是自己畫的一些草圖,本人自認畫得非常爛,希望各位看官諒解o(∩_∩)o

 

 

---------------------------------------------- 華麗的分割線 ---------------------------------------------------------

先創建一個表,保存DBCC IND的結果

 1 CREATE TABLE DBCCResult (
 2 PageFID NVARCHAR(200),
 3 PagePID NVARCHAR(200),
 4 IAMFID NVARCHAR(200),
 5 IAMPID NVARCHAR(200),
 6 ObjectID NVARCHAR(200),
 7 IndexID NVARCHAR(200),
 8 PartitionNumber NVARCHAR(200),
 9 PartitionID NVARCHAR(200),
10 iam_chain_type NVARCHAR(200),
11 PageType NVARCHAR(200),
12 IndexLevel NVARCHAR(200),
13 NextPageFID NVARCHAR(200),
14 NextPagePID NVARCHAR(200),
15 PrevPageFID NVARCHAR(200),
16 PrevPagePID NVARCHAR(200)
17 )

創建一個聚集索引表

1 --只有聚集索引
2 CREATE TABLE Department(
3     DepartmentID int IDENTITY(1,1) NOT NULL PRIMARY KEY,
4     Name NVARCHAR(200) NOT NULL,
5     GroupName NVARCHAR(200) NOT NULL,
6     Company NVARCHAR(300),
7     ModifiedDate datetime NOT NULL  DEFAULT (getdate())
8 )

插入10W條記錄

1 INSERT INTO Department(name,[Company],groupname) VALUES('銷售部','中國你好有限公司XX分公司','銷售組')
2 GO 100000

將DBCC IND的結果放入DBCCRESULT表

1 INSERT INTO DBCCResult EXEC ('DBCC IND(pratice,Department,-1) ')

查詢Department表中的頁面情況

先說明一下:

PageType  分頁類型: 1:數據頁面;2:索引頁面;3:Lob_mixed_page;4:Lob_tree_page;10:IAM頁面

IndexID     索引ID:    0 代表堆, 1 代表聚集索引, 2-250 代表非聚集索引 ,大於250就是text或image字段

紅色框部分都是需要關注的

第一個:IAM頁不是隻有堆表纔有也不只是維護堆表中的數據頁的連續,有索引的表都有,所以IAM頁不只維護數據頁,也維護索引頁的連續,在下篇說到非聚集索引的時候

我會給出MSDN的解釋和IAM頁在聚集索引表,非聚集索引表中的情況

第二個:每個數據頁的IndexID都是1,不是說數據頁變成了索引頁,而是說現在數據頁已經屬於聚集索引的一部分,不在堆裏了

第三個:每個數據頁的IndexLevel都是0,就是說數據頁在聚集索引的最下層

第四個:索引頁和數據頁,前一頁和後一頁是首尾相連的,但是數據頁和索引頁不是首尾相連的,也就是說沒有一個數據頁的[PrevPagePID]指向14464頁或3528頁

那麼在上面的聚集索引圖片中爲什麼會說索引頁指向數據頁呢?葉子節點就是數據頁呢?

數據頁的index level是0,那麼就是說聚集索引的葉子節點就是數據頁

 上面索引頁的結構

現在來看一下索引頁裏都有什麼,運行下面的SQL語句

 1 DBCC TRACEON(3604,-1)
 2 GO
 3 
 4 DBCC PAGE([pratice],1,3527,3)
 5 GO
 6 
 7 
 8 DBCC PAGE([pratice],1,3528,3)
 9 GO
10 
11 DBCC PAGE([pratice],1,14464,3)
12 GO

 您們應該看到ChildPageId,所以上面我的圖爲什麼會這樣畫的原因,索引頁連接着數據頁,而且一個索引頁指向多個數據頁

 DepartmentID是主鍵列,從1開始自增,那麼從下圖可以看出主鍵列數據是從最左邊的索引節點(不是葉子節點)開始排序

這裏有個問題:爲什麼根節點只有兩行???是不是根節點只作連接作用,所以只有兩行 ,不過這個問題我也不清楚

聚集索引頁裏主鍵列DepartmentID上一行與下一行相差120條記錄,一個數據頁剛好容納120條記錄

KeyHashValue根據主鍵列的第一個字段而生成的,就算兩個表完全一樣,這個hash出來的KeyHashValue都不會一樣

我創建了一個一模一樣的表Department2,看到hash出來的值都不一樣

而這個KeyHashValue我們就叫做 鍵,也就是key-value中的key

------------------------------------------------------------ 華麗的分割線 ------------------------------------------------------

聚集索引怎麼找記錄的???

這裏要分兩種情況:(1)聚集索引查找和(2)聚集索引掃描

(1)聚集索引查找

 放大一下索引頁

 SQLSERVER首先把每個數據頁的頭一條記錄裏的DepartmentID的值加上一定範圍值hash出一個key值,然後放在KeyHashValue列裏

當我要找DepartmentID爲110的那條記錄裏的GroupName和Company的值的時候,首先SQLSERVER根據where DepartmentID=110

將110加上一個範圍值hash出一個值,這個值就是KeyHashValue的值,找到KeyHashValue=(f000ff86397c)的那條記錄

然後到數據頁13791裏找出DepartmentID爲110的那條記錄裏的GroupName和Company的值

 其實這裏的算法應該跟hash join是一樣的,但是實際具體怎麼算的?本人就不清楚了,大家可以看一下hash join的原理

個人感覺在SQLSERVER裏 key-value hash桶用途很廣泛,執行計劃、 hash join、 聚集索引都用到了

證明:這裏我可以證明一下SQLSERVER聚集索引查找記錄的流程

先到索引頁裏找到鍵值爲KeyHashValue=XXX的那條記錄,然後再到數據頁裏把實際數據讀出來

 運行下面的SQL語句,看一下SQLSERVER申請的鎖就知道了

 下面實驗我在Department2表裏做的,表數據和表結構和Department1一模一樣

 1 --只有聚集索引
 2 CREATE TABLE Department2(
 3     DepartmentID int IDENTITY(1,1) NOT NULL PRIMARY KEY,
 4     Name NVARCHAR(200) NOT NULL,
 5     GroupName NVARCHAR(200) NOT NULL,
 6     Company NVARCHAR(300),
 7     ModifiedDate datetime NOT NULL  DEFAULT (getdate())
 8 )
 9 
10 INSERT INTO Department2(name,[Company],groupname) VALUES('銷售部','中國你好有限公司XX分公司','銷售組')
11 GO 100000
12 
13 
14 SELECT * FROM Department2
15 
16 --先清空[DBCCResult]表裏的記錄
17 --TRUNCATE TABLE [dbo].[DBCCResult]
18 INSERT INTO DBCCResult EXEC ('DBCC IND(pratice,Department2,-1) ')
19 
20 SELECT * FROM [dbo].[DBCCResult] ORDER BY [PageType] DESC 
21 
22 DBCC PAGE([pratice],1,14471,3)
23 GO
24 
25 DBCC PAGE([pratice],1,4375,3)
26 GO
27 DBCC PAGE([pratice],1,4376,3)
28 GO
View Code

 下面這個證明代碼在《SQLSERVER企業級平臺管理實踐》裏找的

 1 USE [pratice]
 2 GO
 3 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
 4 GO
 5 --以下查詢使用了聚集索引查找 ctrl+l
 6 BEGIN TRAN
 7 SELECT GroupName FROM [dbo].[Department2]  WHERE DepartmentID IN(32641,361,32281) 
 8 
 9 --COMMIT TRAN
10 
11 USE [pratice] --要查詢申請鎖的數據庫
12 GO
13 SELECT
14 [request_session_id],
15 c.[program_name],
16 DB_NAME(c.[dbid]) AS dbname,
17 [resource_type],
18 [request_status],
19 [request_mode],
20 [resource_description],OBJECT_NAME(p.[object_id]) AS objectname,
21 p.[index_id]
22 FROM sys.[dm_tran_locks] AS a LEFT JOIN sys.[partitions] AS p
23 ON a.[resource_associated_entity_id]=p.[hobt_id]
24 LEFT JOIN sys.[sysprocesses] AS c ON a.[request_session_id]=c.[spid]
25 WHERE c.[dbid]=DB_ID('pratice') AND a.[request_session_id]=@@SPID  ----要查詢申請鎖的數據庫
26 ORDER BY [request_session_id],[resource_type]
View Code

 

(2)聚集索引掃描

先drop掉Department2表,然後重新創建Department2表

 1 --只有聚集索引
 2 CREATE TABLE Department2(
 3     DepartmentID int IDENTITY(1,1) NOT NULL PRIMARY KEY,
 4     Name NVARCHAR(200) NOT NULL,
 5     GroupName NVARCHAR(200) NOT NULL,
 6     Company NVARCHAR(300),
 7     ModifiedDate datetime NOT NULL  DEFAULT (getdate())
 8 )
 9 
10 DECLARE @i INT
11 SET @i=1
12 WHILE @i < 100000 
13     BEGIN
14         INSERT  INTO Department3 ( name, [Company], groupname )
15         VALUES  ( '銷售部', '中國你好有限公司XX分公司'+CAST(@i AS VARCHAR(200)), '銷售組'+CAST(@i AS VARCHAR(200)) )
16         SET @i = @i + 1
17     END
18 
19 
20 SELECT * FROM Department2
View Code

證明:

 1 USE [pratice]
 2 GO
 3 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
 4 GO
 5 --以下查詢使用了聚集索引查找 ctrl+l
 6 BEGIN TRAN
 7 SELECT * FROM [dbo].[Department2]  WHERE [GroupName] ='銷售組83421'
 8 
 9 --COMMIT TRAN
10 
11 USE [pratice] --要查詢申請鎖的數據庫
12 GO
13 SELECT
14 [request_session_id],
15 c.[program_name],
16 DB_NAME(c.[dbid]) AS dbname,
17 [resource_type],
18 [request_status],
19 [request_mode],
20 [resource_description],OBJECT_NAME(p.[object_id]) AS objectname,
21 p.[index_id]
22 FROM sys.[dm_tran_locks] AS a LEFT JOIN sys.[partitions] AS p
23 ON a.[resource_associated_entity_id]=p.[hobt_id]
24 LEFT JOIN sys.[sysprocesses] AS c ON a.[request_session_id]=c.[spid]
25 WHERE c.[dbid]=DB_ID('pratice') AND a.[request_session_id]=@@SPID  ----要查詢申請鎖的數據庫
26 ORDER BY [request_session_id],[resource_type]
View Code

上圖“以下查詢使用了聚集索引查找”,由於本人寫SQL代碼的時候沒有修改上面註釋,大家可以不用理會

爲什麼會有一個鍵鎖,那麼多的頁鎖,在徐海蔚老師的《SQLSERVER企業級平臺管理實踐》的書本里第361頁說到

因爲在有聚集索引的表格上,數據是直接存放在索引的最底層(葉子節點),所以要掃描整個表格裏的數據,就要把整個聚集索引

掃描一遍。在這裏,聚集索引掃描就相當於一個表掃描。所要用的時間和資源與表掃描沒有什麼差別

再看一下聚集索引查找的流程

SQLSERVER首先把每個數據頁的頭一條記錄裏的DepartmentID的值加上一定範圍值hash出一個key值,然後放在KeyHashValue列裏

當我要找DepartmentID爲110的那條記錄裏的GroupName和Company的值的時候,首先SQLSERVER根據where DepartmentID=110

將110加上一個範圍值hash出一個值,這個值就是KeyHashValue的值,找到KeyHashValue=(f000ff86397c)的那條記錄

然後到數據頁13791裏找出DepartmentID爲110的那條記錄裏的GroupName和Company的值

因爲[GroupName]列不是索引列,所以根本找不到KeyHashValue值,所以這裏只能使用掃描所有數據頁的方法來找出記錄,除非找到那條記錄

不然SQLSERVER不會停止掃描數據頁,所以纔看到上圖有那麼多的頁面上加了頁鎖,SQLSERVER需要逐個數據頁逐個數據頁去掃描就像堆表的全表掃描那樣。

那個鍵鎖我估計是當SQLSERVER找到那條記錄之後,需要在

記錄的所在頁面(即是索引頁指向那個記錄的數據頁的那一行)加上一個鍵鎖,以防止別人刪除索引頁的那一行記錄

但是聚集索引掃描是不是一定比聚集索引查找要差呢?這個不一定,要看實際情況o(∩_∩)o

那麼非聚集索引掃描是不是跟聚集索引掃描一樣,所要用的時間和資源與表掃描沒有什麼差別呢???

大家可以看一下《SQLSERVER聚集索引與非聚集索引的再次研究(下)》本人做的一個小實驗

實驗證明了《SQLSERVER企業級平臺管理實踐》裏第363頁說到的內容

索引掃描表明SQLSERVER正在掃描一個非聚集索引。由於非聚集索引上一般只會有一小部分字段,所以這裏雖然也是掃描,但是

代價會比整表掃描要小很多

 ------------------------------------------------ 華麗的分割線 --------------------------------------------------------------------

 這裏有一個問題:沒有主鍵但是有聚集索引,索引頁的列數不一樣,會多了一列,而這個列(uniquifier)的作用在下面會講到

這裏創建Department3表

 1 --只有聚集索引
 2 CREATE TABLE Department3(
 3     DepartmentID int IDENTITY(1,1) NOT NULL ,
 4     Name NVARCHAR(200) NOT NULL,
 5     GroupName NVARCHAR(200) NOT NULL,
 6     Company NVARCHAR(300),
 7     ModifiedDate datetime NOT NULL  DEFAULT (getdate())
 8 )
 9 
10 CREATE CLUSTERED INDEX CL_DepartmentID ON [dbo].[Department3]([DepartmentID])
11 
12 DECLARE @i INT
13 SET @i=1
14 WHILE @i < 100000 
15     BEGIN
16         INSERT  INTO Department3 ( name, [Company], groupname )
17         VALUES  ( '銷售部', '中國你好有限公司XX分公司'+CAST(@i AS VARCHAR(200)), '銷售組'+CAST(@i AS VARCHAR(200)) )
18         SET @i = @i + 1
19     END
20 
21 
22 SELECT * FROM Department3
23 
24 --TRUNCATE TABLE [dbo].[DBCCResult]
25 
26 INSERT INTO DBCCResult EXEC ('DBCC IND(pratice,Department3,-1) ')
27 
28 SELECT * FROM [dbo].[DBCCResult] ORDER BY [PageType] DESC 
29 
30 DBCC PAGE([pratice],1,13861,3)
31 GO
View Code

 可以看到只有聚集索引沒有主鍵的表會比主鍵表多了一列uniquifier列,這個列的作用會在創建Department5表的時候講到

----------------------------------------------- 華麗的分割線 -------------------------------------------------------

 下面說一下,複合主鍵或者聚集索引建立在多個字段上,KeyHashValue只會根據第一個字段生成hash key

當你查詢的時候where 後面的字段不包含創建聚集索引時的第一個字段或者複合主鍵的第一個字段就會聚集索引掃描而不是聚集索引查找

 創建Department4表

 1 --只有聚集索引
 2 CREATE TABLE Department4  --包含複合主鍵DepartmentID 和Name
 3 (
 4   DepartmentID INT IDENTITY(1, 1) NOT NULL ,
 5   Name NVARCHAR(200) NOT NULL ,
 6   GroupName NVARCHAR(200) NOT NULL ,
 7   Company NVARCHAR(300) ,
 8   ModifiedDate DATETIME NOT NULL DEFAULT ( GETDATE() ) ,
 9   CONSTRAINT [PK_Department4_1] PRIMARY KEY CLUSTERED
10     ( DepartmentID ASC,
11       Name ASC )
12     WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
13            ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]
14 ) ON [PRIMARY]
15 
16 
17 DECLARE @i INT
18 SET @i=1
19 WHILE @i < 100000 
20     BEGIN
21         INSERT  INTO Department4 ( name, [Company], groupname )
22         VALUES  ( '銷售部'+CAST(@i AS VARCHAR(200)), '中國你好有限公司XX分公司', '銷售組' )
23         SET @i = @i + 1
24     END
25 
26 
27 SELECT * FROM [dbo].[Department4]
28 
29 
30 
31 --TRUNCATE TABLE [dbo].[DBCCResult]
32 INSERT INTO DBCCResult EXEC ('DBCC IND(pratice,Department4,-1) ')
33 
34 SELECT * FROM [dbo].[DBCCResult] ORDER BY [PageType] DESC 
35 
36 DBCC PAGE([pratice],1,7102,3)
37 GO
View Code

1 SELECT * FROM [dbo].[Department4] WHERE name='銷售部6'  --聚集索引掃描  因爲name不是複合主鍵中的第一個字段
2 SELECT * FROM [dbo].[Department4] WHERE name='銷售部241' AND [DepartmentID]=241  --聚集索引查找
3 SELECT * FROM [dbo].[Department4] WHERE [DepartmentID]=241   --聚集索引查找

 

 

在建立聚集索引的時候在多個字段上建立聚集索引是沒有任何意義的

因爲聚集索引查找是根據建立索引的第一個字段來查找,索引掃描的時候會到數據頁裏掃描 ,而聚集索引的每一行只是一個數據頁的範圍值從而不能直接定位到要找的那條記錄

所以只需要在數據表的一個字段上建立聚集索引就可以了,而究竟要在哪一個字段上建立聚集索引大家一定好好斟酌,本人建議那一個字段在order by中經常要排序的

因爲數據頁都已經按照聚集索引的第一個字段排好序的了

而不像非聚集索引的索引頁跟數據表的記錄一一對應,掃描的時候掃描索引頁的每一行

大家可以對比一下聚集索引和非聚集索引頁的結構

聚集索引頁的結構

非聚集索引頁的結構

非聚集索引頁面的結構會在SQLSERVER聚集索引與非聚集索引的再次研究(下)裏講到

 --------------------------------------------------------- 華麗的分割線 -----------------------------------------------------

 由於主鍵不允許重複值,那麼就在表上創建一個不唯一的聚集索引,有人說在重複值很多的列上建立聚集索引沒有意義

創建Department5表  在Company字段上建立聚集索引,Company字段的值全部都是"中國你好有限公司XX分公司"

 1 --只有聚集索引
 2 USE [pratice]
 3 GO
 4 CREATE TABLE Department5
 5 (
 6   DepartmentID INT IDENTITY(1, 1) NOT NULL ,
 7   Name NVARCHAR(200) NOT NULL ,
 8   GroupName NVARCHAR(200) NOT NULL ,
 9   Company NVARCHAR(300) ,
10   ModifiedDate DATETIME NOT NULL  DEFAULT ( GETDATE() ) 
11 )
12 
13 CREATE CLUSTERED INDEX CL_Company ON [dbo].[Department5]([Company] ASC)
14 
15 --DROP TABLE [dbo].[Department5]
16 
17 DECLARE @i INT
18 SET @i=1
19 WHILE @i < 10000
20     BEGIN
21         INSERT  INTO Department5 ( name, [Company], groupname )
22         VALUES  ( '銷售部'+CAST(@i AS VARCHAR(200)), '中國你好有限公司XX分公司', '銷售組' )
23         SET @i = @i + 1
24     END
View Code
1 --TRUNCATE TABLE [dbo].[DBCCResult]
2 INSERT INTO DBCCResult EXEC ('DBCC IND(pratice,Department5,-1) ')
3 
4 SELECT * FROM [dbo].[DBCCResult] ORDER BY [PageType] DESC 
5 
6 DBCC PAGE([pratice],1,14516,3)
7 GO
View Code

 

在Department3表的時候講到列(uniquifier),爲什麼有主鍵的表沒有這個列,而聚集索引的表有這個列,原因在於

主鍵列不能有重複值,必須是唯一的,而聚集索引允許有重複值,所以聚集索引需要增加列(uniquifier)來區分重複值

而且可以看到這裏uniquifier列是沒有規律的,不像Department表每隔120行記錄在索引頁裏標記一行

看一下執行計劃和執行結果

 1 SET STATISTICS TIME ON
 2 SELECT * FROM [dbo].[Department5] WHERE [Company]='中國你好有限公司XX分公司' AND [DepartmentID]=241 
 3 
 4 SQL Server 分析和編譯時間: 
 5    CPU 時間 = 0 毫秒,佔用時間 = 0 毫秒。
 6 
 7 (1 行受影響)
 8 
 9 SQL Server 執行時間:
10    CPU 時間 = 0 毫秒,佔用時間 = 0 毫秒。

1 SET STATISTICS TIME ON
2 SELECT * FROM [dbo].[Department5] WHERE name='銷售部106' AND [DepartmentID]=106  --聚集索引掃描
3 SQL Server 執行時間:
4    CPU 時間 = 0 毫秒,佔用時間 = 0 毫秒。

至於應不應該在重複值很多的列上建立聚集索引我這裏也不敢妄下判斷,因爲實際環境和這裏的測試環境不一樣

在MSDN中的解釋: http://msdn.microsoft.com/zh-cn/library/ms177484(v=SQL.105).aspx

如果聚集索引不是唯一的索引,SQL Server 將添加在內部生成的值(稱爲 唯一值 )以使所有重複鍵唯一。此四字節的值對於用戶不可見

---------------------------------------------------- 華麗的分割線 -------------------------------------------------------

 堆表中的數據頁之間[PrevPagePID],[NextPagePID]是否會首尾相連

堆表

聚集索引表

 ----------------------------------------------------- 華麗的分割線 -------------------------------------------------

 聚集索引有一個特點,就是當表記錄太少的時候不會生成任何索引頁,當記錄達到一定數量才生成索引頁這個數量我現在還不清楚

但是就算沒有索引頁,SQLSERVER還會使用聚集索引查找,這個問題的確奇怪

創建Department6表,然後插入9條記錄

 1 --只有聚集索引
 2 USE [pratice]
 3 GO
 4 CREATE TABLE Department6
 5 (
 6   DepartmentID INT IDENTITY(1, 1) NOT NULL ,
 7   Name NVARCHAR(200) NOT NULL ,
 8   GroupName NVARCHAR(200) NOT NULL ,
 9   Company NVARCHAR(300) ,
10   ModifiedDate DATETIME NOT NULL DEFAULT ( GETDATE() ) ,
11   CONSTRAINT [PK_Department6_1] PRIMARY KEY CLUSTERED
12     (  Name ASC,DepartmentID ASC )
13     WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
14            ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]
15 ) ON [PRIMARY]
16 
17 
18 DECLARE @i INT
19 SET @i=1
20 WHILE @i < 10
21     BEGIN
22         INSERT  INTO Department6 ( name, [Company], groupname )
23         VALUES  ( '銷售部'+CAST(@i AS VARCHAR(200)), '中國你好有限公司XX分公司', '銷售組' )
24         SET @i = @i + 1
25     END

只有一個數據頁和一個IAM頁

插入更多記錄

1 DECLARE @i INT
2 SET @i=1
3 WHILE @i < 100000
4     BEGIN
5         INSERT  INTO Department6 ( name, [Company], groupname )
6         VALUES  ( '銷售部'+CAST(@i AS VARCHAR(200)), '中國你好有限公司XX分公司', '銷售組' )
7         SET @i = @i + 1
8     END

開始有索引頁了

--------------------------------------------- 華麗的分割線 ---------------------------------------------------

大家再看一下Department2表的那部分,究竟數據頁的排序順序跟主鍵DepartmentID的排序順序有沒有關係呢?

先創建Department7表,插入1000條記錄

 1 CREATE TABLE Department7(
 2     DepartmentID int IDENTITY(1,1) NOT NULL PRIMARY KEY,
 3     Name NVARCHAR(200) NOT NULL,
 4     GroupName NVARCHAR(200) NOT NULL,
 5     Company NVARCHAR(300),
 6     ModifiedDate datetime NOT NULL  DEFAULT (getdate())
 7 )
 8 
 9 INSERT INTO Department7(name,[Company],groupname) VALUES('銷售部','中國你好有限公司XX分公司','銷售組')
10 GO 1000
11 
12 SELECT * FROM Department7
View Code
1 --TRUNCATE TABLE [dbo].[DBCCResult]
2 INSERT INTO DBCCResult EXEC ('DBCC IND(pratice,Department7,-1) ')
3 
4 SELECT * FROM [dbo].[DBCCResult] ORDER BY [PageType] DESC

根據數據頁的首尾連接順序,我畫了一下草圖

看一下索引頁13791

1 DBCC TRACEON(3604,-1)
2 GO
3 
4 DBCC PAGE([pratice],1,13791,3)
5 GO
View Code

再畫一下草圖

對比一下 數據頁的首尾連接順序那張圖,不知道大家看出規律沒有

所以我把聚集索引結構圖畫成下面這個樣子

爲什麼聚集索引只能按照第一個字段生成key?爲什麼數據頁只能按照第一個字段來排序?

其實這個跟數據頁排序有關的,大家再仔細看下面兩張圖

聚集索引頁里根據第一個字段排列好這些數據頁的第一個字段的範圍值,數據頁根據這個範圍值首尾相連一一排序好

如果聚集索引按多個字段來排序,那麼數據頁根本排不了,多個字段又升序,又降序??那怎麼排序啊?只能按照一個字段來排序

聚集索引查找的時候,使用order by爲什麼這麼快,因爲數據已經根據索引第一個字段排好序了,例子中的字段就是DepartmentID

而只有非聚集索引的表order by的時候就需要排一下序了,因爲表中沒有聚集索引,數據頁沒有預先按照一定順序來排序

詳細可以看一下非聚集索引的結構:SQLSERVER聚集索引與非聚集索引的再次研究(下)

--------------------------------------------- 華麗的分割線 --------------------------------------------------------

問題:爲什麼一個表只能建立一個聚集索引

其實大家看一下我上面畫的聚集索引結構圖和非聚集索引結構圖就知道了

因爲如果一個表有聚集索引,那麼他的數據頁跟索引頁有非常強的聯繫,數據頁跟主鍵第一個字段排好序了,例子中就是“DepartmentID”

如果你再建一個聚集索引,你叫SQLSERVER應該按哪個字段來排序?排序方式是按照你原來的那個聚集索引的DepartmentID列來排序還是

按照你新建的那個聚集索引的第一個字段來排序??

多個聚集索引,數據頁都按不同的字段順序排序,來建立雙向鏈表,那數據表不就亂套了???

但是如果一個表中只有非聚集索引,非聚集索引裏的索引頁的每一行會有一個指針值指向數據頁,數據頁依然是堆,沒有任何順序可言

所以你可以在一個表上建立多個非聚集索引也沒問題

至於表裏面只有非聚集索引表結構是怎樣的,大家可以看一下本系列的《SQLSERVER聚集索引與非聚集索引的再次研究(下)》

到時大家就會更加清楚了o(∩_∩)o

---------------------------------------------- 華麗的分割線 ----------------------------------------------------------

還有一個問題沒有解決:

爲什麼根節點只有兩行???是不是根節點只作連接作用,所以只有兩行 ?

聚集索引就說到這裏了,有些地方有可能不對,希望大家強烈拍磚o(∩_∩)o

也希望給個推薦o(∩_∩)o


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章