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で、列_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バイトには何が含まれていますか?
Paul Randalは、実際にリンクしたブログ投稿のコメントで、この正確な質問に回答しています。
8060バイトは、1レコードの最大サイズであり、ページのデータ領域の量ではなく、8096バイトです。
最大サイズのレコードが8060バイトの場合、スロットアレイエントリに2バイト、可能なヒープ転送レコードバックポインタに10バイト、可能なバージョニングタグに14バイトを追加し、26バイトが使用されます。残りの10バイトは、将来使用するためのものです。
ページに複数のレコードがある場合、8096バイトのデータ領域をすべて使用できます。
それで、あなたの投稿の本文の質問に答えて:
これらの36バイトには何が含まれていますか?
ページ内の「余分な」36バイトは、次のように使用されます。
質問で定義されたテーブルが実際には8060バイト幅であることを確認するために、完全な再現を行ってみましょう。
まず、データベースとテーブルを設定し、1つの行を挿入します。ヒープが最悪なので、クラスタ化インデックスを追加しています。
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
以下は、そのコマンドの出力からのいくつかの重要なスニペットです。 HEADERセクションから:
m_freeCnt = 34
これは、ページに34バイトの空き領域があることを意味します。これは、元の投稿で概説した36から、スロット配列エントリの2バイトを引いたものです。そういえば:
m_slotCnt = 1
つまり、このページにはレコードが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バイトになります。したがって、4行で最大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
これでページが見つかりました。予想どおり1つだけです。
DBCC IND ('PageJunk', 'QuarterPage', 1);
GO
それでは、352ページの情報を取得します。
DBCC PAGE (PageJunk, 1, 352, 3);
GO
そしてここに良いものがあります:
m_slotCnt = 4
m_freeCnt = 0
空き容量なし!このページは4行でいっぱいです。