1. 理解數據頁結構

以下來自Woodytu的sqlserver存儲系列,一共八篇,記錄下來學習

https://www.cnblogs.com/woodytu/p/4484328.html

 

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

SQL Server8KB的頁來存儲數據,並且在SQL Server裏磁盤 I/O 操作在頁級執行。SQL Server把數據記錄存在數據頁(Data Page)裏。數據記錄是堆表裏、聚集索引裏葉子節點的行。數據頁由3個部分組成。頁頭(標頭),數據區(數據行和可用空間)及行偏移量

https://images0.cnblogs.com/blog2015/750348/201505/071022449383068.png

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

IF EXISTS ( SELECT  *

             FROM    sysobjects

             WHERE   id = OBJECT_ID(N'[dbo].[Customers]')

                     AND OBJECTPROPERTY(id, N'IsUserTable') = 1 )

     DROP TABLE dbo.Customers

 

 CREATE TABLE Customers

 (

    FirstName CHAR(50) NOT NULL,

    LastName CHAR(50) NOT NULL,

    Address CHAR(100) NOT NULL,

    ZipCode CHAR(5) NOT NULL,

    Rating INT NOT NULL,

    ModifiedDate DATETIME NOT NULL,

 )

 GO

 INSERT INTO dbo.Customers

         ( FirstName ,

           LastName ,

           Address ,

           ZipCode ,

           Rating ,

           ModifiedDate

         )

 VALUES  ( 'Woody' , -- FirstName - char(50)

           'Tu' , -- LastName - char(50)

           'ZUOQIAO YOUXI TOWN LINHAI CITY' , -- Address - char(50)

           '0000' , -- ZipCode - char(5)

           1 , -- Rating - int

           '2015-05-07 10:09:51'  -- ModifiedDate - datetime

         )

    go

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

DBCC IND 命令用於查詢一個存儲對象的內部存儲結構信息,可以通過對象名/id查到對應pageid,fileid等再通過DBCC PAGE查看對應頁和文件具體信息

 

該命令有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跟蹤標誌.

我們來執行下列的命令:

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

SQL Server會給我們如下的輸出結果: https://images0.cnblogs.com/blog2015/750348/201505/071049502821717.png

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

 

DBCC IND輸出字段描述

Column()

Meaning(含義)

PageFID

索引所在文件ID

PagePID

索引頁ID

IAMFID

File ID of the IAM managing this pageIAM pageIAMFID=NULL

IAMPID

Page ID of the IAM managing this pageIAM pageIAMPID=NULL

ObjectID

對象ID

IndexID

索引類型ID0表示堆,1表示聚集索引,2-250表示非聚集索引。可以在sys.indexs上查找

PartitionNumber

Partition number within the table or index for this page

PartitionID

ID for the partition containing this page (unique in the database)

iam_chain_type

Type of allocation unit this page belongs to: in-row data, row-overflow data, or LOB data

PageType

Page type: 1 = data page, 2 = index page, 3 = LOB_MIXED_PAGE, 4 = LOB_TREE_PAGE, 10 = IAM page

IndexLevel

索引級別,0表示葉子節點,根節點的級別最高

NextPageFID

File ID for next page at this level,同一等級上下一個索引頁所在文件的ID

NextPagePID

Page ID for next page at this level

PrevPageFID

File ID for previous page at this level

PrevPagePID

Page ID for previous page at this level,同一等級上的上一個索引頁,通過雙向鏈表連接起來

 

我們得到了數據頁的頁號爲79,現在我們來看看79號數據頁裏存放的數據,這個就要用到DBCC PAGE命令,它的語法如下:

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

 DBCC TRACEON(3604)

 DBCC PAGE(InternalStorageFormat,1,79,3)

 GO    

SQL Server會給我們包含4個部分的輸出。

第1部分是BUFFER,裏面是一些內存分配信息,對此我們沒多少興趣。

第2部分是固定96 bytes大小的頁頭(page header),頁頭會類似如下顯示:

https://images0.cnblogs.com/blog2015/750348/201505/071106074856975.png

頁頭相關字段的含義:

  • 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                         頁的校驗位或者被由數據庫頁面保護形式決定分頁保護位取代

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

https://images0.cnblogs.com/blog2015/750348/201505/071209206889397.png

  • 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 的系統行開銷。

https://images0.cnblogs.com/blog2015/750348/201505/071304414239628.png

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

DBCC TRACEON(3604)

DBCC PAGE(InternalStorageFormat,1,79,1)

GO   

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

https://images0.cnblogs.com/blog2015/750348/201505/071317161571953.png

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

https://images0.cnblogs.com/blog2015/750348/201505/071341019545963.png

在頁頭我們看到當前頁面還有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。下列的表創建語句會失敗。

 CREATE TABLE Maxsize(

 id         CHAR(8000) NOT NULL,

 id1        CHAR(54) NOT NULL

 )

 

https://images0.cnblogs.com/blog2015/750348/201505/071353138762191.png

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

 

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