web-dev-qa-db-ja.com

ページヘッダーに格納されるその他の情報

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バイトには何が含まれていますか?

参考資料

5

Paul Randalは、実際にリンクしたブログ投稿のコメントで、この正確な質問に回答しています。

8060バイトは、1レコードの最大サイズであり、ページのデータ領域の量ではなく、8096バイトです。

最大サイズのレコードが8060バイトの場合、スロットアレイエントリに2バイト、可能なヒープ転送レコードバックポインタに10バイト、可能なバージョニングタグに14バイトを追加し、26バイトが使用されます。残りの10バイトは、将来使用するためのものです。

ページに複数のレコードがある場合、8096バイトのデータ領域をすべて使用できます。

それで、あなたの投稿の本文の質問に答えて:

これらの36バイトには何が含まれていますか?

ページ内の「余分な」36バイトは、次のように使用されます。

  • ヒープ転送レコードバックポインタ用に10バイトが予約されています
  • tempdbのバージョンストアを指すバージョニングタグ用に14バイト予約
  • スロットアレイに12バイトが利用可能
    • 1つの大きなレコードについて概説した場合、ここには10バイトの「無駄な」スペースがあります。 2バイトのスロットアレイエントリをさらに5つ入れる余地があります

質問で定義されたテーブルが実際には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

cryptic dbcc nonsense

ページタイプが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

more dbcc nonsense

それでは、352ページの情報を取得します。

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

そしてここに良いものがあります:

m_slotCnt = 4
m_freeCnt = 0

空き容量なし!このページは4行でいっぱいです。

10
Josh Darnell