次のテーブルを作成しました:
CREATE TABLE dbo.TestStructure
(
id INT NOT NULL,
filler1 CHAR(36) NOT NULL,
filler2 CHAR(216) NOT NULL
);
次に、クラスター化インデックスを作成しました。
CREATE CLUSTERED INDEX idx_cl_id
ON dbo.TestStructure(id);
次に、各行に30行を追加し、各サイズは256バイトです(テーブル宣言に基づく):
DECLARE @i AS int = 0;
WHILE @i < 30
BEGIN
SET @i = @i + 1;
INSERT INTO dbo.TestStructure (id, filler1, filler2)
VALUES (@i, 'a', 'b');
END;
「トレーニングキット(試験70-461):Microsoft SQL Server 2012(Itzik Ben-Gan)へのクエリ」の本で読んだ情報に基づいています。
SQL Serverは、ページ内のデータファイル内のデータを内部的に整理します。ページは8 KBの単位であり、単一のオブジェクトに属しています。たとえば、テーブルやインデックスに。ページは、読み取りと書き込みの最小単位です。ページはさらにエクステントに編成されています。エクステントは8つの連続したページで構成されます。エクステントのページは、単一のオブジェクトまたは複数のオブジェクトに属することができます。ページが複数のオブジェクトに属している場合、エクステントは混合エクステントと呼ばれます。ページが単一のオブジェクトに属している場合、エクステントは均一エクステントと呼ばれます。 SQL Serverは、オブジェクトの最初の8ページを混合エクステントに格納します。オブジェクトが8ページを超えると、SQL Serverはこのオブジェクトに追加の均一エクステントを割り当てます。この構成により、小さなオブジェクトは無駄なスペースが減り、大きなオブジェクトは断片化されにくくなります。
したがって、ここでは、最初の混合エクステント8KBページに7680バイト(256バイトサイズの行を30回挿入したため、30 * 256 = 7680を挿入)があり、サイズチェックプロシージャを実行してサイズを確認しています-次の結果を返します
index_type_desc: CLUSTERED INDEX
index_depth: 1
index_level: 0
page_count: 1
record_count: 30
avg_page_space_used_in_percent: 98.1961947121324
name : TestStructure
rows : 30
reserved : 16 KB
data : 8 KB
index_size : 8 KB
unused : 0 KB
したがって、16 KBはテーブル用に予約されています。最初の8 KBページはルートIAMページ用、2番目は8 KBのリーフデータストレージページ用で、占有率は7.5 KB以下で、256バイトの新しい行を挿入すると次のようになります。
INSERT INTO dbo.TestStructure (id, filler1, filler2)
VALUES (1, 'a', 'b');
256バイト(7680 b + 256 = 7936、8KBよりも小さい)のスペースがあっても、同じページに格納されず、新しいデータページが作成されますが、その新しい行は同じ古いページに収まる可能性があります。 、SQL Serverが既存のページに挿入してスペースと検索時間を節約できるのに、なぜ新しいページを作成するのですか?
注:ヒープインデックスでも同じことが起こります。
データ行が256バイトではありません。それぞれが263バイトのようです。純粋に固定長のデータ型のデータ行には、SQL Serverのデータ行の構造により、追加のオーバーヘッドがあります。このサイトを見て、データ行がどのように構成されるかについて読んでください。 http://aboutsqlserver.com/2013/10/15/sql-server-storage-engine-data-pages-and-data-rows/
したがって、あなたの例では、256バイトのデータ行があり、ステータスビットに2バイト、列数に2バイト、データ長に2バイト、そしてnullビットマップにさらに1バイト追加します。つまり、263 * 30 = 7,890バイトです。さらに263を追加すると、8 KBのページ制限を超えてしまい、別のページが作成されることになります。
SQL Serverが8k(8192バイト)のデータページを使用して1つ以上の行を格納することは事実ですが、各データページにはオーバーヘッド(96バイト)があり、各行にはオーバーヘッド(少なくとも9バイト)があります。 8192バイトは純粋なデータではありません。
これがどのように機能するかの詳細な調査については、次のDBA.SEの質問に対する私の回答を参照してください。
DATALENGTHの合計がsys.allocation_unitsのテーブルサイズと一致しない
そのリンクされた回答の情報を使用すると、実際の行サイズをより明確に把握できます。
_DBCC PAGE
_を使用すると、_Record Size 263
_(tempdb
の場合)、および_Record Size 277
_(_ALLOW_SNAPSHOT_ISOLATION ON
_に設定されているデータベースの場合)を表示して、計算を確認します。
これで、30行になります。
WITHOUTバージョン情報
30 * 263は7890バイトになります。次に、使用した7986バイトのページヘッダーに96バイトを追加します。最後に、スロットアレイの60バイト(行ごとに2つ)を追加して、ページで使用される合計8046バイト、残りの146バイトを追加します。 _DBCC PAGE
_を使用すると、次のように表示され、計算が確認されます。
m_slotCnt 30
_(つまり、行数)m_freeCnt 146
_(つまり、ページに残っているバイト数)m_freeData 7986
_(つまり、データ+ページヘッダー-7890 + 96-スロット配列は、「使用済み」バイトの計算に含まれません)WITHバージョン情報
30 * 277バイト、合計8310バイト。しかし、8310は8192を超えており、96バイトのページヘッダーも2バイトの行スロット配列(30 * 2 = 60バイト)も考慮されていなかったため、行に使用できるのは8036バイトだけでした。
しかし、29行はどうですか?これにより、データは8033バイト(29 * 277)+ページヘッダー用96バイト+スロット配列用58バイト(29 * 2)となり、8187バイトになります。そして、それはページを残り5バイトのままにします(もちろん8192-8187;もちろん使用不可)。 _DBCC PAGE
_を使用すると、次のように表示され、計算が確認されます。
m_slotCnt 29
_(つまり、行数)m_freeCnt 5
_(つまり、ページに残っているバイト数)m_freeData 8129
_(つまり、データ+ページヘッダー-8033 + 96-スロット配列は、「使用済み」バイトの計算に考慮されません)ヒープについて
ヒープは、データページをわずかに異なる方法で埋めます。彼らは、ページに残っているスペースの量の非常に大まかな見積もりを維持します。 DBCC出力を確認するときは、PAGE HEADER: Allocation Status PFS (1:1)
の行を確認してください。 _0x60 MIXED_EXT ALLOCATED 0_PCT_FULL
_(クラスターテーブルを参照した場合)または_0x64 MIXED_EXT ALLOCATED 100_PCT_FULL
_(ヒープテーブルを参照した場合)の行に沿ってVALUE
が何かを表示しているのがわかります。これはトランザクションごとに評価されるため、ここで実行されているテストなどの個別の挿入を実行すると、クラスター化テーブルとヒープテーブルで異なる結果が表示される可能性があります。ただし、30行すべてに対して単一のDML操作を実行すると、期待どおりにヒープがいっぱいになります。
ただし、ヒープに関するこれらの詳細は、この特定のテストに直接影響しません。これは、テーブルの両方のバージョンが30行に収まり、残りのバイト数は146バイトだけであるためです。クラスター化またはヒープに関係なく、別の行に十分なスペースではありません。
このテストはかなり単純であることを覚えておいてください。行の実際のサイズの計算は、SPARSE
、データ圧縮、LOBデータなどのさまざまな要因によって非常に複雑になる可能性があります。
データページの詳細を表示するには、次のクエリを使用します。
_DECLARE @PageID INT,
@FileID INT,
@DatabaseID SMALLINT = DB_ID();
SELECT @FileID = alloc.[allocated_page_file_id],
@PageID = alloc.[allocated_page_page_id]
FROM sys.dm_db_database_page_allocations(@DatabaseID,
OBJECT_ID(N'dbo.TestStructure'), 1, NULL, 'DETAILED') alloc
WHERE alloc.[previous_page_page_id] IS NULL -- first data page
AND alloc.[page_type] = 1; -- DATA_PAGE
DBCC PAGE(@DatabaseID, @FileID, @PageID, 3) WITH TABLERESULTS;
_
** 14バイトの「バージョン情報」値は、データベースが_ALLOW_SNAPSHOT_ISOLATION ON
_または_READ_COMMITTED_SNAPSHOT ON
_に設定されている場合に存在します。
データページの実際の構造は非常に複雑です。一般的に、ページごとに8060バイトがユーザーデータに使用できると述べられていますが、この動作につながるオーバーヘッドはどこにもカウントされません。
ただし、SQL Serverが実際に31行目がページに収まらないというヒントを提供していることに気づいたかもしれません。次の行が同じページに収まるように、avg_page_space_used_in_percent
値は100%-(100/31)= 96.774194未満である必要があり、あなたの場合はそれをはるかに上回ります。
追伸私は、Kalen Delaneyによる "SQL Server Internals"の本の1つでデータページ構造の詳細なバイト説明まで見たと思いますが、それは約10年前だったので、これ以上詳細を覚えていません。さらに、ページ構造はバージョンごとに変わる傾向があります。