SQL Server 存儲(1/8):理解數據頁結構


我們都很清楚SQL Server用8KB 的頁來存儲數據,並且在SQL Server里磁盤 I/O 操作在頁級執行。也就是說,SQL Server 讀取或寫入所有數據頁。頁有不同的類型,像數據頁,GAM,SGAM等。在這文章里,讓我們一起來理解下數據頁結構。

SQL Server把數據記錄存在數據頁(Data Page)里。數據記錄是堆表里、聚集索引里葉子節點的行。

數據頁由3個部分組成。頁頭(標頭),數據區(數據行和可用空間)及行偏移數組。

在我們討論在SQL Server里,數據頁內部結構具體是什么樣之前,我們來創建一個表並插入一些記錄。

 

 1 USE [InternalStorageFormat]
 2 GO
 3 
 4 IF EXISTS ( SELECT  *
 5             FROM    sysobjects
 6             WHERE   id = OBJECT_ID(N'[dbo].[Customers]')
 7                     AND OBJECTPROPERTY(id, N'IsUserTable') = 1 )
 8     DROP TABLE dbo.Customers
 9 
10 CREATE TABLE Customers
11 (
12    FirstName CHAR(50) NOT NULL,
13    LastName CHAR(50) NOT NULL,
14    Address CHAR(100) NOT NULL,
15    ZipCode CHAR(5) NOT NULL,
16    Rating INT NOT NULL,
17    ModifiedDate DATETIME NOT NULL,
18 )
19 GO
20 
21 
22 INSERT INTO dbo.Customers
23         ( FirstName ,
24           LastName ,
25           Address ,
26           ZipCode ,
27           Rating ,
28           ModifiedDate
29         )

30 VALUES  ( 'Woody' , -- FirstName - char(50)
31           'Tu' , -- LastName - char(50)
32           'ZUOQIAO YOUXI TOWN LINHAI CITY' , -- Address - char(50)
33           '0000' , -- ZipCode - char(5)
34           1 , -- Rating - int
35           '2015-05-07 10:09:51'  -- ModifiedDate - datetime
36         )
37         go 2

現在我們要找出SQL Server給這個表分配的頁有哪些,這個就要用到非文檔的命令DBCC IND。
它的語法如下:

DBCC IND 命令用於查詢一個存儲對象的內部存儲結構信息,該命令有4個參數, 前3個參數必須指定。語法如下:
DBCC IND ( { 'dbname' | dbid }, { 'objname' | objid },{ nonclustered indid | 1 | 0 | -1 | -2 } [, partition_number] )
第一個參數是數據庫名或數據庫ID。
第二個參數是數據庫中的對象名或對象ID,對象可以是表或者索引視圖。
第三個參數是一個非聚集索引ID或者 1, 0, 1, or 2. 值的含義:
 0: 只顯示對象的in-row data頁和 in-row IAM 頁。
 1: 顯示對象的全部頁, 包含IAM 頁, in-row數據頁, LOB 數據頁row-overflow 數據頁 . 如果請求的對象含有聚集所以則索引頁也包括。
 -1: 顯示全部IAM頁,數據頁, 索引頁 也包括 LOB 和row-overflow 數據頁。
 -2: 顯示全部IAM頁。
 Nonclustered index ID:顯示索引的全部 IAM頁, data頁和索引頁,包含LOB和 row-overflow數據頁。
為了兼容sql server 2000,第四個參數是可選的,該參數用於指定一個分區號.如果不給定值或者給定0, 則顯示全部分區數據。
和DBCC PAGE不同的是, SQL Server運行DBCC IND不需要開啟3604跟蹤標志.

我們來執行下列的命令:

1 DBCC IND('InternalStorageFormat','Customers',-1)

SQL Server會給我們如下的輸出結果:

可以看到有2條記錄,一條記錄為頁面類型(PageType)為10的頁和一條記錄為頁面類型(PageType)為1的頁。頁面類型(PageType)10是IAM頁,頁面類型(PageType)1是數據頁,它的頁ID是79.

關於數據庫頁類型如下所示:

  • 1 Data page 堆表和聚集索引的葉子節點數據
  • 2 Index page 聚集索引的非葉子節點和非聚集索引的所有索引記錄

  • 3 Text mixed page A text page that holds small chunks of LOB values plus internal parts of text tree. These can be shared between LOB values in the same partition of an index or heap.

  • 4 Text tree page A text page that holds large chunks of LOB values from a single column value.

  • 7 Sort page 排序時所用到的臨時頁,排序中間操作存儲數據用的。

  • 8 GAM page 全局分配映射(Global Allocation Map,GAM)頁面 這些頁面記錄了哪些區已經被分配並用作何種用途。

  • 9 SGAM page 共享全局分配映射(Shared Global Allocation Map,GAM)頁面 這些頁面記錄了哪些區當前被用作混合類型的區,並且這些區需含有至少一個未使用的頁面。

  • 10 IAM page  有關每個分配單元中表或索引所使用的區的信息

  • 11 PFS page  有關頁分配和頁的可用空間的信息

  • 13 boot page 記錄了關於數據庫的信息,僅存於每個數據庫的第9頁

  • 15 file header page 記錄了關於數據庫文件的信息,存於每個數據庫文件的第0頁

  • 16 DCM page 記錄自從上次全備以來的數據改變的頁面,以備差異備份

  • 17 BCM page 有關每個分配單元中自最后一條 BACKUP LOG 語句之后的大容量操作所修改的區的信息

現在我們來看看79號類型為1的數據頁里存放的數據,這個就要用到DBCC PAGE命令,它的語法如下:

dbcc page 命令讀取數據頁結構的命令DBCC Page。
該命令為非文檔化的命令,具體如下:
  DBCC Page ({dbid|dbname},filenum,pagenum[,printopt])
  具體參數描述如下:
  dbid 包含頁面的數據庫ID
  dbname 包含頁面的數據庫的名稱
  filenum 包含頁面的文件編號
  pagenum 文件內的頁面
  printopt 可選的輸出選項;選用其中一個值:
  0:默認值,輸出緩沖區的標題和頁面標題
  1:輸出緩沖區的標題、頁面標題(分別輸出每一行),以及行偏移量表
  2:輸出緩沖區的標題、頁面標題(整體輸出頁面),以及行偏移量表
  3:輸出緩沖區的標題、頁面標題(分別輸出每一行),以及行偏移量表;每一行
  后跟分別列出的它的列值
  要想看到這些輸出的結果,還需要設置DBCC TRACEON(3604)。

我們來執行下列的命令:

1 DBCC TRACEON(3604)
2 DBCC PAGE(InternalStorageFormat,1,79,3)
3 GO    

SQL Server會給我們包含4個部分的輸出。第1部分是BUFFER,里面是一些內存分配信息,對此我們沒多少興趣。下一部分是固定96 bytes大小的頁頭(page header),頁頭(page header)會類似如下顯示:

頁頭相關字段的含義:

  • Page @0x08F84000            同BUFFER中的bpage地址
  • m_pageId = (1:79)              數據頁號     
  • m_headerVersion = 1         頭文件版本號,一直為1          
  • m_type = 1                          頁面類型,1為數據頁面
  • m_typeFlagBits = 0x4         數據頁和索引頁為4,其他頁為0        
  • m_level = 0                         該頁在索引頁(B樹)中的級數
  • m_flagBits = 0x8000          頁面標志
  • m_objId (AllocUnitId.idObj) = 46                       同Metadata: ObjectId             
  • m_indexId (AllocUnitId.idInd) = 256                  同Metadata: IndexId
  • Metadata: AllocUnitId = 72057594040942592  存儲單元的ID,sys.allocation_units.allocation_unit_id                              
  • Metadata: PartitionId = 72057594039304192   數據頁所在的分區號,sys.partitions.partition_id                             
  • Metadata: IndexId = 0                                        頁面的索引號,sys.objects.object_id&sys.indexes.index_id
  • Metadata: ObjectId = 277576027                      該頁面所屬的對象的id,sys.objects.object_id
  • m_prevPage = (0:0)                  該數據頁的前一頁面;主要用在數據頁、索引頁和IAM頁
  • m_nextPage = (0:0)                  該數據頁的后一頁面;主要用在數據頁、索引頁和IAM頁
  • pminlen = 221                          定長數據所占的字節數
  • m_slotCnt = 2                           頁面中的數據的行數
  • m_freeCnt = 7644                    頁面中剩余的空間
  • m_freeData = 544                    從第一個字節到最后一個字節的空間字節數
  • m_reservedCnt = 0                   活動事務釋放的字節數
  • m_lsn = (255:8406:2)                日志記錄號
  • m_xactReserved = 0                 最新加入到m_reservedCnt領域的字節數
  • m_xdesId = (0:0)                       添加到m_reservedCnt的最近的事務id
  • m_ghostRecCnt = 0                 幻影數據的行數
  • m_tornBits = 0                         頁的校驗位或者被由數據庫頁面保護形式決定分頁保護位取代

再來看下頁面相關分配情況:

 

  • GAM (1:2) = ALLOCATED                                                   在GAM頁上的分配情況
  • SGAM (1:3) = ALLOCATED                                                 在SGAM頁上的分配情況
  • PFS (1:1) = 0x61 MIXED_EXT ALLOCATED  50_PCT_FULL 在PFS頁上的分配情況,該頁為50%滿,                       
  • DIFF (1:6) = CHANGED
  • ML (1:7) = NOT MIN_LOGGED   

接下來就是用於存放實際數據的槽(slot),每條記錄存放一個槽(slot)里。0號槽在頁里擁有第1條數據,1號槽擁有第2條數據,以此類推。通過下面的圖片,你可以看到我們記錄大小是224 bytes,217 bytes(50+50+100+5+4+8) 的定長和7 bytes 的系統行開銷。

頁的最后一部分是行偏移數組表,我們可以用參數為1的DBCC PAGE命令來,在輸出信息的底部獲得。

執行如下的命令:

1 DBCC TRACEON(3604)
2 DBCC PAGE(InternalStorageFormat,1,79,3)
3 GO    

SQL Server在輸出信息的底部,給我們如下的信息:

這個行偏移表,應該從下往上讀。每條槽條目是一個2 bytes長的指針指向頁里槽偏移量。這里我們插入了2條記錄,所以表里有2個槽條目。第1條記錄指向第96 bytes,剛好在頁頭后。這個行偏移表可以幫助我們管理頁面的記錄。在頁里的行偏移表里,每條記錄需要2 bytes的大小來存儲。於此類似,在堆表上建立的非聚集索引,每個非聚集索引行里都包含一個物理指針映射回堆表里的行記錄。這個物理指針是[文件號:頁號:槽號](file:page:solt)的結構,因此在讀取頁的時候,可以找到堆表里的對應行,再通過行偏移表里槽號里的偏移量,就可以在頁里讀取到對應的行記錄。如果我們要修改頁中間的記錄,我們並不一定需要重組整個頁,我們只要修改偏移表里偏移量即可。

在頁頭我們看到當前頁面還有7644 bytes可以用,我們一起來驗證下。

(8 * 1024) - 96 - (217 * 2)-(7 * 2)-(2 * 2)=7644 bytes

8 * 1024 = 頁的總大小,8K

         96 = 頁頭大小 96 bytes       

 217 * 2 = 每條記錄的總長 * 記錄數

     7 * 2 = 每條記錄的系統行開銷 * 記錄數

     2 * 2 = 行偏移表里每槽占用字節數 * 記錄數

現在我們已經知道了頁的結構,我們一起來小結下。

頁是 8KB 的大小,即 8192 bytes,固定 96 bytes的大小給頁頭使用,接下來是具體的數據以槽的方式存儲。數據記錄的最大長度是 8060 bytes(包括 7 bytes的系統行開銷),因此一條記錄中你擁有的最大字節數是 8053 bytes。下列的表創建語句會失敗。

1 CREATE TABLE Maxsize(
2 id         CHAR(8000) NOT NULL,
3 id1        CHAR(54) NOT NULL
4 )

剩下的 36 bytes (8192-96-8060)保留給槽數組(Slot array)或者任何轉發行返回指針(forwarding row back pointer)(每條10 bytes)。這就意味一個頁不一定就能保存18(36/2)條記錄。槽數組(Slot array)根據你的記錄數從下往上增長。如果記錄長度小,頁里就可以存儲更多的記錄,偏移表也會自下而上占用更多的空間。 

參考文章:

http://www.sqlservercentral.com/blogs/practicalsqldba/2012/08/12/sql-server-understanding-the-data-page-structure/


免責聲明!

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



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