以下來自Woodytu的sqlserver存儲系列,一共八篇,記錄下來學習
https://www.cnblogs.com/woodytu/p/4484328.html
SQL Server用8KB的頁來存儲數據,並且在SQL Server裏磁盤 I/O 操作在頁級執行。SQL Server把數據記錄存在數據頁(Data Page)裏。數據記錄是堆表裏、聚集索引裏葉子節點的行。數據頁由3個部分組成。頁頭(標頭),數據區(數據行和可用空間)及行偏移量。
在我們討論在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會給我們如下的輸出結果:
可以看到有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 page,IAM page的IAMFID=NULL |
IAMPID |
Page ID of the IAM managing this page,IAM page的IAMPID=NULL |
ObjectID |
對象ID |
IndexID |
索引類型ID,0表示堆,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),頁頭會類似如下顯示:
頁頭相關字段的含義:
- 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命令來,在輸出信息的底部獲得。
DBCC TRACEON(3604)
DBCC PAGE(InternalStorageFormat,1,79,1)
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。下列的表創建語句會失敗。
CREATE TABLE Maxsize(
id CHAR(8000) NOT NULL,
id1 CHAR(54) NOT NULL
)
剩下的 36 bytes (8192-96-8060)保留給槽數組(Slot array)或者任何轉發行返回指針(forwarding row back pointer)(每條10 bytes)。這就意味一個頁不一定就能保存18(36/2)條記錄。槽數組(Slot array)根據你的記錄數從下往上增長。如果記錄長度小,頁裏就可以存儲更多的記錄,偏移表也會自下而上佔用更多的空間。