通過DBCC PAGE查看頁信息驗證聚集索引和非聚集索引節點信息


前言

在閱讀之前可以參考之前的一篇博客:通過DBCC IND分析表組織和索引組織

1.DBCC PAGE基礎

page是sql server中最小的IO單位,在數據庫中如果我們只是查詢一行記錄,也會讀取這一行所在的整個頁信息。

那么page里面是如何存儲信息的呢?我們可以通過dbcc page解析頁的相信信息。當我們知道page是如何存儲數據以后,對於我們后面解析聚集索引和非聚集索引的葉子節點非常有幫助。因為聚集索引的葉子節點是data page,那么我們dbcc page聚集索引的葉子節點,得到的應該就是真實的數據。如果我們dbcc pag非聚集索引的葉子節點,得到的可能是聚集索引的鍵值,也可能得到的是具體file:page:slot這樣的信息,這個叫做RID指向具體的數據行。如果使用過dbcc page,可能對slot不陌生。這時候我們也終於明白為什么說非聚集索引的葉子節點是行標示符的意思了。

首先通過以下實驗來解析一頁存儲一條記錄的情況。

USE TESTDB3;
GO
--1.創建表
CREATE TABLE Clustered_Dupes
(
    Col1 char(5)   NOT NULL, --5字節
    Col2 int     NOT NULL,   --4字節 
    Col3 char(3)   NULL,     --3字節
    Col4 char(6)   NOT NULL  --6字節
);
GO
--2.此時沒有索引,索引查詢結果indid=0,表示堆結構,keycnt=0,表示沒有key
SELECT  [first],[indid],[keycnt],[name] FROM sysindexes WHERE id = object_id ('Clustered_Dupes');

--3.創建聚集索引
CREATE CLUSTERED INDEX Cl_dupes_col1 ON Clustered_Dupes(col1);

--4.此時有聚集索引了,所以indid=1,keycnt=2不明白
SELECT  [first],[indid],[keycnt],[name] FROM sysindexes WHERE id = object_id ('Clustered_Dupes');


--5.插入一行數據
INSERT Clustered_Dupes VALUES ('ABCDE', 123, null, 'CCCC');

--6.查看表的頁信息
dbcc ind ( TESTDB3, Clustered_Dupes, -1)

--7.找到data page,並使用dbcc page查看data page 信息
DBCC TRACEON (3604);
GO
DBCC PAGE (TESTDB3,1,2206, 1);
--DBCC PAGE (TESTDB3,1,2206, 1) with tableresults;

執行完第7不以后的結果如下圖所示,我們將解析其中的主要信息:

首先我們發現length=25,但是我們發現我們的四個列的字段長度加起來只有5+4+3+6=18個字節,為什么是25呢?之前寫過一篇文章:SQL Server計算數據庫中表、堆、聚集索引和非聚集索引的大小。通過這篇文章中提到的方法,我們可以算出來,row size=25,也就是這里的length。其實上圖中的一行加起來也剛還是25字節。

仔細觀察可以發現,數據是以16進制形式存儲的,所以每2位是一個字節,表示一個字符。我們已經將4列的信息標注出來了。

上圖說Col1中的"ABCDE"在頁中的存儲形式是“4142434445”,這是通過ASCII來存儲,如下圖所示,我們發現字符A在ASCII表中對應的hex的值是41。

接下來是Col2中存儲的123對應頁中"7b000000",這是因為123的16進制等於7b,這個我們可以使用calc計算得到。

Col3中插入的是NULL,在page中以00000000來表示,這也可以理解為什么Col2中7b以后使用000000來填充。

Col4中我們插入的是“CCCC”,那么在Page中應該是“43434343”,但是上圖顯示的是“434343432020”,查看ASCII碼表我們發現20表示的是空格。而我們Col4的定義是:

Col4 char(6)

索引在Col4長度不到6的時候,用space,也就是20來填充page中的信息。而我們Col1因為長度為5,剛好又插入了5個字母,所以才沒有出現填充。我們可以通過以下實驗來驗證。

--8.再次插入一條記錄
INSERT Clustered_Dupes VALUES ('ABCD', 123, null, '中國');

--再次查看,發現m_slotCnt由1變成2了,DATA中有Slot0跟Slot1.
DBCC PAGE (TESTDB3,1,2206, 1);

查詢結果如下:

DATA:
Slot 0, Offset 0x79, Length 25, DumpStyle BYTE
Record Type = PRIMARY_RECORD         Record Attributes =  NULL_BITMAP     Record Size = 25
Memory Dump @0x000000000E18A079
0000000000000000:   10001600 41424344 207b0000 00000000 †....ABCD {...... 
0000000000000010:   d6d0b9fa 20200500 08†††††††††††††††††Öйú  ...    
Slot
1, Offset 0x60, Length 25, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP Record Size = 25 Memory Dump @0x000000000E18A060 0000000000000000: 10001600 41424344 457b0000 00000000 †....ABCDE{...... 0000000000000010: 43434343 20200500 08†††††††††††††††††CCCC ...

從上面的查詢結果,對於第一次執行的查詢,我們發現:

  1. 原來的那條記錄由slot0變成了slot1;
  2. 新插入Col1的值是“ABCD”,只有4個字節,所以在“41424344”后面使用了"20"也就是空格進行填充;
  3. 新插入的Col4是漢字“中國”,對應的是"d6d0b9fa"以及因為沒有達到6字節而出現的2個空格填充“2020”;
  4. 查看漢字與16進制編碼的對照有兩種方法:1.下載6. 編碼:GB2312漢字對應表,查看漢字與16進制的互換。2.使用edit plus,輸入漢字,然后點擊Hex Viewer查看漢字所對應的16進制編碼。

 2.DBCC PAGE在索引節點上的引用

下面我們將通過dbcc page來展示聚集索引的葉子節點。因為我們都說聚集索引的葉子節點就是data page,包含真實的數據,我們可以使用dbcc page來驗證。

2.1.dbcc page聚集索引的葉子節點

首先我們執行如下實驗:

use TESTDB3
--1.創建表,有主鍵,sql server默認設置為聚集索引
CREATE TABLE Suppliers
(
  supplierid   INT          NOT NULL IDENTITY,
  companyname  CHAR(10) NOT NULL,
  address  CHAR(10) NOT NULL,
  CONSTRAINT PK_Suppliers PRIMARY KEY(supplierid)
);

--2.創建非聚集索引
CREATE NONCLUSTERED INDEX idx_nc_companyname ON dbo.Suppliers(companyname);

--3.插入一條記錄
insert into Suppliers values('Microsoft','紫竹');

--4.查看頁信息,結果不為null,有兩條記錄。發現IndexID=1表示聚集索引。
dbcc ind ( TESTDB3, [dbo.Suppliers], -1)

--5.查看數據頁PageType=1的這個page的信息
DBCC PAGE (TESTDB3,1,2184, 1);

上面的主要操作是創建一張含有三個字段的表,在id字段上有聚集索引,在companyname上有非聚集索引,而address字段上沒有索引。然后插入一條記錄,查看聚集索引的葉子節點,也就是index=0,pagetype=1的頁。dbcc page查詢結果如下:

DATA:
Slot 0, Offset 0x60, Length 31, DumpStyle BYTE
Record Type = PRIMARY_RECORD         Record Attributes =  NULL_BITMAP     Record Size = 31
Memory Dump @0x00000000119EA060
0000000000000000:   10001c00 01000000 4d696372 6f736f66 †........Microsof 
0000000000000010:   7420d7cf d6f12020 20202020 030000††††t ×ÏÖñ      ...  

我們首先解析上面的DATA信息。Length=31不再解釋。Microsoft我們可以通過查看ASCII得出他的16進制代碼是"4D 69 63 72 6F 73 6F 66  74"我們在上面已經用相應的顏色標注,而主鍵supplierid的值為1,所對應的值為01000000。最后address的值是“紫竹”,對應上面的16進制代碼是“d7cf d6f12020 20202020”。

這說明:在聚集索引的葉子節點上,包含了一條記錄的所有數據。

2.2.dbcc page非聚集索引的葉子節點,表上有聚集索引

 接着上面的實驗我們繼續執行下面的命令:

--查找出非聚集索引的葉子節點的位置
dbcc ind ( TESTDB3, [dbo.Suppliers], -1)

--5.查看非聚集索引葉子節點上的index page.pagetype=2,indexlevel=0.
DBCC PAGE (TESTDB3,1,2188, 1);

執行DBCC PAGE的查詢結果如下:

Slot 0, Offset 0x60, Length 15, DumpStyle BYTE
Record Type = INDEX_RECORD           Record Attributes =                  Record Size = 15
Memory Dump @0x000000000EDBC060
0000000000000000:   064d6963 726f736f 66742001 000000††††.Microsoft ....  

如上查詢結果所示,在非聚集索引葉子節點上,包含了非聚集索引的key(4d6963 726f736f 667420,對應companyname=Microsoft),如果有聚集索引,那么還包含聚集索引的key(01,對應supplierid=1)。

然后我們再插入一條記錄看看非聚集索引葉子節點上的變化

--7.再插入一條記錄
insert into Suppliers values('Intel','紫竹');

--8.查看非聚集索引葉子節點
DBCC PAGE (TESTDB3,1,2188, 1);

查詢結果如下:

Slot 0, Offset 0x6f, Length 15, DumpStyle BYTE
Record Type = INDEX_RECORD           Record Attributes =                  Record Size = 15
Memory Dump @0x000000000EDBC06F
0000000000000000:   06496e74 656c2020 20202002 000000††††.Intel     ....  

Slot 1, Offset 0x60, Length 15, DumpStyle BYTE
Record Type = INDEX_RECORD           Record Attributes =                  Record Size = 15
Memory Dump @0x000000000EDBC060
0000000000000000:   064d6963 726f736f 66742001 000000††††.Microsoft ....  

從上述查詢結果我們可以得出結論

  1. 新插入的記錄都會寫入到page中的slot0位置,原先的記錄往后移動
  2. 在有聚集索引的情況下,非聚集索引葉子節點記錄的是非聚集索引鍵值與聚集索引的鍵值

2.3.dbcc page非聚集索引的葉子節點,表上沒有聚集索引

本來想通過

drop index PK_Suppliers on Suppliers

來刪除索引的,但是報錯“An explicit DROP INDEX is not allowed on index 'Suppliers.PK_Suppliers'. It is being used for PRIMARY KEY constraint enforcement.”。這是因為聚集索引是由主鍵自動產生的,而不是我們手動創建的。我們重新創建表來做實驗,執行如下命令:

--實驗4:----無聚集索引情況下,非聚集索引葉子節點的數據內容------------------
use TESTDB3
--1.創建表,堆結構
CREATE TABLE Suppliers
(
  supplierid   INT          NOT NULL,
  companyname  CHAR(10) NOT NULL,
  address  CHAR(10) NOT NULL,
);

--2.創建非聚集索引
CREATE NONCLUSTERED INDEX idx_nc_companyname ON dbo.Suppliers(companyname);

--3.插入兩條記錄
insert into Suppliers values(1,'Microsoft','紫竹');
insert into Suppliers values(2,'Intel','紫竹');

--4.查看頁信息,發現有4個page,其中兩個PageType=10,一個PageType=1,還有一個PageType=2
dbcc ind ( TESTDB3, [dbo.Suppliers], -1)

--5.查看PageType=1,index=0的page,這是data page
DBCC PAGE (TESTDB3,1,2184, 1);

其查詢結果如下:

Slot 0, Offset 0x60, Length 31, DumpStyle BYTE
Record Type = PRIMARY_RECORD         Record Attributes =  NULL_BITMAP     Record Size = 31
Memory Dump @0x00000000119EA060
0000000000000000:   10001c00 01000000 4d696372 6f736f66 †........Microsof 
0000000000000010:   7420d7cf d6f12020 20202020 030000††††t ×ÏÖñ      ...  
Slot
1, Offset 0x7f, Length 31, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP Record Size = 31 Memory Dump @0x00000000119EA07F 0000000000000000: 10001c00 02000000 496e7465 6c202020 †........Intel 0000000000000010: 2020d7cf d6f12020 20202020 030000†††† ×ÏÖñ ...

我們發現:

  1. 數據節點的內容與之前的一模一樣,沒有發生改變。也就是說data page不管有沒有聚集索引,是沒有變化的。
  2. Microsoft在slot0上,Intel在slot1上,這個我們后面會用到。

然后我們再來看看在沒有聚集索引以后,非聚集索引的葉子節點的內容,執行如下命令:

--6.查看非聚集索引的葉子節點,其pagetype=2,indexlevel=0.
DBCC PAGE (TESTDB3,1,2188, 1);

查詢結果如下所示:

Slot 0, Offset 0x73, Length 19, DumpStyle BYTE
Record Type = INDEX_RECORD           Record Attributes =                  Record Size = 19
Memory Dump @0x000000000AD8C073
0000000000000000:   06496e74 656c2020 20202088 08000001 †.Intel     ..... 
0000000000000010:   000100†††††††††††††††††††††††††††††††...              

Slot 1, Offset 0x60, Length 19, DumpStyle BYTE
Record Type = INDEX_RECORD           Record Attributes =                  Record Size = 19
Memory Dump @0x000000000AD8C060
0000000000000000:   064d6963 726f736f 66742088 08000001 †.Microsoft ..... 
0000000000000010:   000000†††††††††††††††††††††††††††††††...           

對比2.2中非聚集索引葉子節點index page的內容與現在index page中的內容。我們發現:

  1. 兩者前面的數據內容是一樣的,比如"064d6963 726f736f 667420",其中紅字部分對應的是"Microsoft",
  2. 如果有聚集索引,我們發現其后面跟多是聚集索引的鍵值,現在沒有聚集索引,其后面跟所的不再是聚集索引的鍵值,而是row identifier (RID,行標識符), 格式為"File#:Page#:Slot#"。那么"88 08000001 000100"對應的就是RID。可以參考之前的博客:Sql Server中的表組織和索引組織(聚集索引結構,非聚集索引結構,堆結構)
  3. 我們發現Intel的最后六位是000100,而Microsoft的最后六位是000000,假如這個是Slot的話,那么剛好跟我們之前提到的"Microsoft在slot0上,Intel在slot1上,這個我們后面會用到。"相一致。但是前面的"88 08000001"不會解析。
  4. 在<inside in sql server2005>第七章的Clustered Index Node Rows小結提到了關於file:page:slot的東西。前面提到了RID="88 08000001 000100"是正確的,不過正確的分割應該是RID="88080000:0100:0100",對應的應該是RID="PAGE:FILE:SLOT"。還有一個需要注意的是高位存儲的問題。比如RID="88080000:0100:0100"應該翻譯成RID="0x00000888:0x0001:0x0001"="2184:1:1",這剛好對應我們查找data page的出來的內容。

2.4.DBCC PAGE聚集索引非葉子節點

前面我們dbcc page的都是葉子節點上面的數據,現在我們對聚集索引非葉子節點執行dbcc page,查看其中內容。

執行如下實驗:

--實驗4:查看聚集索引非葉子節點--------------------------------------
use TESTDB3
--1.創建表,堆結構
CREATE TABLE Suppliers
(
  supplierid   INT          NOT NULL,
  companyname  CHAR(10) NOT NULL,
  address  CHAR(10) NOT NULL,
);

--2.創建聚集索引
CREATE CLUSTERED INDEX idx_nc_supplierid ON Suppliers(supplierid);

--3.插入1000條記錄
SET NOCOUNT ON;
GO
DECLARE @i int;
SET @i = 1;
WHILE @i <= 1000 BEGIN
  INSERT INTO Suppliers
   SELECT @i, 'Microsoft', '紫竹';
  SET @i = @i + 1;
 END;
GO

--4.查看是否插入成功
select * from Suppliers;

--5.查看頁信息,找出PageType=2,indexleve=1的聚集索引非葉子節點.
dbcc ind ( TESTDB3, [dbo.Suppliers], -1)

--6.查看PagePID=2188的頁
DBCC PAGE (TESTDB3,1,2188, 3);--產看child page
DBCC PAGE (TESTDB3,1,2188, 1);
-------------------------------------------------------------------

dbcc page的查詢結果如下

DATA:
Slot 0, Offset 0x60, Length 11, DumpStyle BYTE
Record Type = INDEX_RECORD           Record Attributes =                  Record Size = 11
Memory Dump @0x000000000AD8C060
0000000000000000:   06000000 00880800 000100†††††††††††††...........      
Slot 1, Offset 0x6b, Length 11, DumpStyle BYTE
Record Type = INDEX_RECORD           Record Attributes =                  Record Size = 11
Memory Dump @0x000000000AD8C06B
0000000000000000:   06f60000 008d0800 000100†††††††††††††.ö.........      
Slot 2, Offset 0x76, Length 11, DumpStyle BYTE
Record Type = INDEX_RECORD           Record Attributes =                  Record Size = 11
Memory Dump @0x000000000AD8C076
0000000000000000:   06eb0100 008e0800 000100†††††††††††††.ë...Ž.....      
Slot 3, Offset 0x81, Length 11, DumpStyle BYTE
Record Type = INDEX_RECORD           Record Attributes =                  Record Size = 11
Memory Dump @0x000000000AD8C081
0000000000000000:   06e00200 008f0800 000100†††††††††††††.à.........      
Slot 4, Offset 0x8c, Length 11, DumpStyle BYTE
Record Type = INDEX_RECORD           Record Attributes =                  Record Size = 11
Memory Dump @0x000000000AD8C08C
0000000000000000:   06d50300 00900800 000100†††††††††††††.Õ.........    

同樣是在<inside in sql server2005>第七章的Clustered Index Node Rows小結,我找到如下內容:

"When you examine index pages, you need to be aware that the first index key entry on each page is frequently either meaningless or empty. The down-page pointer is valid, but the data for the index key might not be a valid value. When SQL Server traverses the index, it starts looking for a value by comparing the search key with the second key value on the page. If the value being sought is less than the second entry on the page, SQL Server follows the page pointer indicated in the first index entry. In this example, the down-page pointer is at byte offsets 6 through 9, with a hex value of 0x5264. (The next two bytes are 0x0001 for the file ID.) In decimal, the page number for the first page at the next level down is 21092, which is the same value we saw earlier when looking at the output of DBCC IND."

通過上述結果以及引用文字,我們可以得出結論:

  1. 在我們的例子中,index page中的內容是"06000000 00880800 000100",那么他的第6-9字節表示下一頁的PagePID,第10-11位表示下一頁的PageFID。按照前面的只是,PagePID="880800 00"="0x00000888"=2184,PageFID="0100"=0x0001=1。我們可以通過DBCC PAGE (TESTDB3,1,2188, 3)來驗證這一事實。
  2. 前面分析了6-9跟10-11這兩段內容,接下來我們看一下第2-5這一段內容。正如引文中說的,sql server 遍歷索引的時候,總是將search key與slot1上的key值進行比較,如果search key值小於slot1上的key,那么會繼續從slot0指向的page去查找。我們查看從slot1到slot4上第2-5為上的數據如下所示。我們可以發現246 491 736 981剛好是等差數列,等差為245,而246減去supplierid的第一個值1也剛好是245。所以我們這里第2-5位的內容求出來的是主鍵鍵值。
slot1 "f60000 00"=0x00f6=246
slot2 "eb0100 00"=0x01eb=491
slot3 "e00200 00"=0xf02e0=736
slot4 "d50300 00"=0x03d5=981

 

 

 

 


免責聲明!

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



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