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

咱們都很清楚SQL Server用8KB 的頁來存儲數據,而且在SQL Server裏磁盤 I/O 操做在頁級執行。也就是說,SQL Server 讀取或寫入全部數據頁。頁有不一樣的類型,像數據頁,GAM,SGAM等。在這文章裏,讓咱們一塊兒來理解下數據頁結構。html

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

數據頁由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。
它的語法以下:sqlserver

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跟蹤標誌.
spa

咱們來執行下列的命令:3d

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

SQL Server會給咱們以下的輸出結果:
指針

能夠看到有2條記錄,一條記錄爲頁面類型(PageType)爲10的頁和一條記錄爲頁面類型(PageType)爲1的頁。頁面類型(PageType)10是IAM頁,頁面類型(PageType)1是數據頁,它的頁ID是79.日誌

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

  • 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/

相關文章
相關標籤/搜索