Sql-Server

頁眉中還儲存了哪些其他資訊

  • July 11, 2018

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 的列定義更改為Data3char(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 個字節包含什麼?

參考資料

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

神秘的 dbcc 廢話

頁麵類型為 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

更多dbcc廢話

所以現在我們要獲取第 352 頁的資訊:

DBCC PAGE (PageJunk, 1, 352, 3);
GO

這是好東西:

m_slotCnt = 4
m_freeCnt = 0

沒有可用空間!這一頁充滿了我們的 4 行。

引用自:https://dba.stackexchange.com/questions/211762