頁眉中還儲存了哪些其他資訊
SQL Server 數據庫頁面的大小定義為 8192 字節。有一些標頭資訊據說是 96 字節大小。
如果您曾經嘗試創建一個包含超過 8053 字節列定義的表,那麼您將遇到錯誤消息:
Creating or altering table 'Generated_Data_GUID' failed because the minimum row size would be 8061, including 7 bytes of internal overhead. This exceeds the maximum allowable table row size of 8060 bytes.
下面是一個範例表 DDL:
CREATE TABLE [dbo].[Generated_Data_GUID]( [ID] [int] IDENTITY(1,1) NOT NULL, [GUID] [uniqueidentifier] NOT NULL, [SEQGUID] [uniqueidentifier] NOT NULL, [Data1] [char](4000) NULL, [Data2] [char](4000) NULL, [Data3] [char](9) NULL, [EntryDate] [datetime2](7) NULL ) ON [PRIMARY]
使用上面的 DDL,如果我將 column 的列定義更改為
Data3
,char(10)
那麼我將遇到錯誤消息。每種列類型的字節大小如下:
int : 4 bytes uniqueidentifiere : 16 bytes char(n) : n bytes datetime2(n) : 6 bytes if n < 3 7 bytes if n = 3 or n = 4 8 bytes if n > 4
如果我們做一些簡單的數學運算,那麼我們最終得到以下計算:
Page Size : 8192 bytes ----------- Header : 96 bytes - Internal Overhead : 7 bytes - Max Size : 8053 bytes - ----------- Missing Data : 36 bytes ===========
問題
這 36 個字節包含什麼?
參考資料
- 頁面和範圍體系結構指南(Microsoft Docs)
- 儲存引擎內部:頁面剖析(SQLSkills.com)
Paul Randal 實際上在您連結到的部落格文章的評論中回答了這個確切的問題:
8060 字節是一條記錄的最大大小,而不是頁面上的數據空間量——8096 字節。
對於 8060 字節的最大記錄,為槽數組條目添加 2 個字節,為可能的堆轉發記錄反向指針添加 10 個字節,為可能的版本控制標記添加 14 個字節,即使用了 26 個字節。其他 10 個字節供將來可能使用。
如果頁面上有多個記錄,則可以使用所有 8096 字節的數據空間。
因此,在回答您文章正文中的問題時:
這 36 個字節包含什麼?
頁面中的“額外”36 個字節的使用如下:
為堆前向記錄反向指針保留 10 個字節
為指向 tempdb 中版本儲存的版本控制標記保留 14 個字節
12 個字節可用於插槽數組
- 在您概述一條大記錄的情況下,這裡有 10 個字節的“浪費”空間。還有 5 個 2 字節插槽數組條目的空間
只是為了確認問題中定義的表實際上是 8060 字節寬,讓我們進行完整的複制。
首先,我們將設置數據庫和表,並在其中插入一行。我正在添加一個聚集索引,因為堆是最糟糕的。
USE master; GO CREATE DATABASE PageJunk; GO USE PageJunk; GO CREATE TABLE [dbo].[Generated_Data_GUID]( [ID] [int] IDENTITY(1,1) NOT NULL, [GUID] [uniqueidentifier] NOT NULL, [SEQGUID] [uniqueidentifier] NOT NULL, [Data1] [char](4000) NULL, [Data2] [char](4000) NULL, [Data3] [char](9) NULL, [EntryDate] [datetime2](7) NULL ) ON [PRIMARY]; GO CREATE CLUSTERED INDEX PK_Generated_Data_GUID ON Generated_Data_GUID (ID); GO INSERT INTO [dbo].[Generated_Data_GUID] ([GUID], SEQGUID, Data1, Data2, Data3, EntryDate) VALUES (NEWID(), NEWID(), REPLICATE('1', 4000), REPLICATE('2', 4000), REPLICATE('3', 9), '2018-01-01'); GO
我們可以通過執行以下 DBCC 命令查看分配索引的所有頁面:
DBCC IND ('PageJunk', 'Generated_Data_GUID', 1); GO
頁麵類型為 1 的頁面是索引頁面(頁面 ID 336)。我們可以使用其他 DBCC 命令轉儲有關該頁面的各種資訊:
DBCC TRACEON (3604); -- needed for the next one to work GO DBCC PAGE (PageJunk, 1, 336, 3); GO
以下是該命令輸出的一些重要片段。從標題部分:
m_freeCnt = 34
這意味著頁面上有 34 個字節的可用空間。那是您在原始文章中概述的 36,減去插槽數組條目的 2 個字節。說到這裡:
m_slotCnt = 1
這意味著該頁面上只有一條記錄。
現在,在記錄部分:
Slot 0 Offset 0x60 Length 8060 Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP Record Size = 8060
這表明儲存在此頁面上的單個記錄為 8060 字節(這是所有數據類型儲存大小的總和加上每條記錄 7 字節的成本)。
所以我們在這個頁面上確實有一個完整大小的 8060 字節記錄。但是,如果我們更加努力的話,我們仍然可以在這個頁面上再增加 34 個字節。
例如,我可以創建一個 2015 字節寬的表。然後,每一行將佔用頁面中的 2015 + 7(內部成本)+ 2(插槽數組)= 2024 個字節。所以四行加起來應該是 8096 字節,正好填滿了 96 字節標題之後剩下的空間。讓我們在同一個數據庫中嘗試一下:
CREATE TABLE [dbo].[QuarterPage]( [ID] [int] IDENTITY(1,1) NOT NULL, [GUID] [uniqueidentifier] NOT NULL, [SEQGUID] [uniqueidentifier] NOT NULL, [Data1] [char](981) NULL, [Data2] [char](981) NULL, [Data3] [char](9) NULL, [EntryDate] [datetime2](7) NULL ) ON [PRIMARY]; GO CREATE CLUSTERED INDEX PK_QuarterPage ON QuarterPage (ID); GO INSERT INTO [dbo].[QuarterPage] ([GUID], SEQGUID, Data1, Data2, Data3, EntryDate) VALUES (NEWID(), NEWID(), REPLICATE('1', 981), REPLICATE('2', 981), REPLICATE('3', 9), '2018-01-01'); GO 4
現在我們找到了我們的頁面,並且正如預期的那樣只有一個:
DBCC IND ('PageJunk', 'QuarterPage', 1); GO
所以現在我們要獲取第 352 頁的資訊:
DBCC PAGE (PageJunk, 1, 352, 3); GO
這是好東西:
m_slotCnt = 4 m_freeCnt = 0
沒有可用空間!這一頁充滿了我們的 4 行。