テーブル内のすべてのレコードのすべてのフィールドのDATALENGTH()
を合計すると、テーブルの合計サイズが得られるという印象を受けました。私は間違っていますか?
_SELECT
SUM(DATALENGTH(Field1)) +
SUM(DATALENGTH(Field2)) +
SUM(DATALENGTH(Field3)) TotalSizeInBytes
FROM SomeTable
WHERE X, Y, and Z are true
_
以下のクエリを使用して(オンラインから取得してテーブルサイズ、クラスター化インデックスのみを取得し、NCインデックスは含まない)、データベース内の特定のテーブルのサイズを取得しました。請求のために(部門に使用するスペースの量に応じて部門に請求します)、この表で各部門が使用したスペースの量を把握する必要があります。テーブル内の各グループを識別するクエリがあります。各グループがどれだけのスペースを使用しているかを知る必要があります。
テーブルのVARCHAR(MAX)
フィールドが原因で、1行あたりのスペースが大きく変動する可能性があるため、平均サイズ*部門の行の比率だけをとることはできません。上記のDATALENGTH()
アプローチを使用すると、以下のクエリで使用される合計スペースの85%しか取得できません。考え?
_SELECT
s.Name AS SchemaName,
t.NAME AS TableName,
p.rows AS RowCounts,
(SUM(a.total_pages) * 8)/1024 AS TotalSpaceMB,
(SUM(a.used_pages) * 8)/1024 AS UsedSpaceMB,
((SUM(a.total_pages) - SUM(a.used_pages)) * 8)/1024 AS UnusedSpaceMB
FROM
sys.tables t with (nolock)
INNER JOIN
sys.schemas s with (nolock) ON s.schema_id = t.schema_id
INNER JOIN
sys.indexes i with (nolock) ON t.OBJECT_ID = i.object_id
INNER JOIN
sys.partitions p with (nolock) ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
INNER JOIN
sys.allocation_units a with (nolock) ON p.partition_id = a.container_id
WHERE
t.is_ms_shipped = 0
AND i.OBJECT_ID > 255
AND i.type_desc = 'Clustered'
GROUP BY
t.Name, s.Name, p.Rows
ORDER BY
TotalSpaceMB desc
_
各部門またはテーブルをパーティション分割するためにフィルター処理されたインデックスを作成することをお勧めします。これにより、インデックスごとに使用される領域を直接クエリできます。フィルター処理されたインデックスは、常にスペースを使用するのではなく、プログラムで作成して(メンテナンスウィンドウや定期的な請求を実行する必要があるときに再度削除することができます)(パーティションはこの点でより優れています)。
私はその提案が好きで、通常そうします。しかし、正直に言うと、「各部門」を例として使用して、なぜこれが必要なのかを説明しますが、正直に言うと、それが実際の理由ではありません。機密保持の理由で、このデータが必要な正確な理由を説明することはできませんが、それはさまざまな部門に類似しています。
このテーブルの非クラスター化インデックスについて:NCインデックスのサイズを取得できれば、それは素晴らしいことです。ただし、NCインデックスはクラスター化インデックスのサイズの1%未満を占めるため、それらを含めなくても問題ありません。しかし、NCインデックスをどのように含めますか?クラスタ化インデックスの正確なサイズを取得することもできません:)
以下の情報は包括的なものではないことに注意してください
計算できるように、データページがどのように配置されるかの説明
非常に複雑であるため、行のセットごとに使用されるバイト数。
8kデータページでスペースを占めるのはデータだけではありません。
予約スペースがあります。 8192バイトのうち8060だけを使用することが許可されています(これは、最初は自分のものではなかった132バイトです)。
DBCC PAGE
によって報告される「レコードサイズ」には含まれません。そのため、以下の行ごとの情報に含まれるのではなく、ここで個別に保持されます。NULL
である列。 8列のセットごとに1バイト。そして、すべての列について、NOT NULL
のものも含みます。したがって、最小1バイトです。ALLOW_SNAPSHOT_ISOLATION ON
またはREAD_COMMITTED_SNAPSHOT ON
のいずれかに設定されている場合に存在します)。行に格納されていないデータのLOBポインター。したがって、DATALENGTH
+ pointer_sizeが考慮されます。しかし、これらは標準サイズではありません。この複雑なトピックの詳細については、次のブログ投稿を参照してください: Varchar、Varbinaryなどの(MAX)タイプのLOBポインターのサイズは? 。そのリンクされた投稿と 私が行った追加のテスト の間で、(デフォルト)ルールは次のようになります。
TEXT
、NTEXT
、およびIMAGE
):text in row
オプションを設定すると、次のようになります。VARCHAR(MAX)
、NVARCHAR(MAX)
、およびVARBINARY(MAX)
)の場合:large value types out of row
オプションを設定するために使用され、その後常にLOBストレージへの16バイトのポインターを使用します。LOBオーバーフローページ:値が10kの場合、1つの完全な8kページのオーバーフローが必要で、次に2ページ目の一部が必要になります。他のデータが残りのスペースを占有できない場合(または許可されている場合でも、そのルールは不明です)、その2番目のLOBオーバーフローデータページに約6kbの「無駄な」スペースがあります。
未使用スペース:8kデータページは8192バイトです。サイズは変わりません。ただし、その上に配置されたデータとメタデータは、常に8192バイトすべてにうまく収まるとは限りません。また、行を複数のデータページに分割することはできません。したがって、残りの100バイトはあるが行が収まらない(または、いくつかの要因によっては、その場所に収まる行がない)場合、データページはまだ8192バイトを占めており、2番目のクエリはデータページ。この値は2か所にあります(この値の一部がその予約済みスペースの一部であることを覚えておいてください)。
DBCC PAGE( db_name, file_id, page_id ) WITH TABLERESULTS;
ParentObject
= "PAGE HEADER:"およびField
= "m_freeCnt"を探します。 Value
フィールドは、未使用のバイト数です。SELECT buff.free_space_in_bytes FROM sys.dm_os_buffer_descriptors buff WHERE buff.[database_id] = DB_ID(N'db_name') AND buff.[page_id] = page_id;
これは、「m_freeCnt」によって報告されるのと同じ値です。これは多くのページを取得できるため、DBCCよりも簡単ですが、最初にページがバッファープールに読み込まれている必要もあります。FILLFACTOR
<100によって予約されたスペース。新しく作成されたページはFILLFACTOR
設定を尊重しませんが、REBUILDを実行すると、各データページにそのスペースが予約されます。予約されたスペースの背後にある考え方は、可変長の列がわずかに多くのデータで更新されるために、ページ上の行のサイズを拡張する非順次挿入または更新、あるいはその両方で使用されるということです(ただし、ページ分割)。ただし、データページのスペースを簡単に予約して、新しい行を取得したり、既存の行を更新したりすることはありません。少なくとも、行のサイズを大きくするような方法で更新されません。
ページ分割(断片化):行を配置するスペースがない場所に行を追加する必要があると、ページ分割が発生します。この場合、既存のデータの約50%が新しいページに移動され、新しい行が2つのページのいずれかに追加されます。しかし、DATALENGTH
の計算では考慮されない、もう少し空き領域があります。
削除対象としてマークされた行。行を削除しても、データページからすぐに削除されるとは限りません。それらをすぐに削除できない場合は、「死のマーク」が付けられ(Steven Segalの参照)、後でゴーストクリーンアッププロセスによって物理的に削除されます(これがその名前だと思います)。ただし、これらはこの特定の質問には関係がない場合があります。
ゴーストページ?これが適切な用語かどうかはわかりませんが、クラスター化インデックスの再構築が完了するまでデータページが削除されない場合があります。また、DATALENGTH
の合計よりも多くのページが考慮されます。これは一般的には起こらないはずですが、私は数年前に一度はそれに遭遇しました。
SPARSE列:スパース列は、行の大部分が1つ以上の列のNULL
であるテーブルのスペース(主に固定長データ型の場合)を節約します。 SPARSE
オプションを指定すると、NULL
の値の型が0バイト上になります(INT
の4バイトなどの通常の固定長の代わりに)、しかし、非NULL値はそれぞれ、追加の4バイトを占めます固定長型と可変長型の可変量。ここでの問題は、DATALENGTH
にはSPARSE列の非NULL値用の余分な4バイトが含まれていないため、これらの4バイトを再度追加する必要があります。SPARSE
列があるかどうかを確認するには、
SELECT OBJECT_SCHEMA_NAME(sc.[object_id]) AS [SchemaName],
OBJECT_NAME(sc.[object_id]) AS [TableName],
sc.name AS [ColumnName]
FROM sys.columns sc
WHERE sc.is_sparse = 1;
そして、各SPARSE
列について、使用するように元のクエリを更新します。
SUM(DATALENGTH(FieldN) + 4)
標準の4バイトを追加する上記の計算は、固定長の型でのみ機能するため、少し単純化されていることに注意してください。さらに、行ごとに追加のメタデータがあり(これまでに私が知ることができます)、少なくとも1つのSPARSE列があるだけで、データに使用できるスペースが減少します。詳細については、MSDNページの Use Sparse Columns を参照してください。
インデックスおよびその他(IAM、PFS、GAM、SGAMなど)のページ:これらは、ユーザーデータの観点からは「データ」ページではありません。これらはテーブルの合計サイズを膨らませます。 SQL Server 2012以降を使用している場合は、sys.dm_db_database_page_allocations
動的管理関数(DMF)を使用してページの種類を確認できます(SQL Serverの以前のバージョンではDBCC IND(0, N'dbo.table_name', 0);
を使用できます)。
SELECT *
FROM sys.dm_db_database_page_allocations(
DB_ID(),
OBJECT_ID(N'dbo.table_name'),
1,
NULL,
N'DETAILED'
)
WHERE page_type = 1; -- DATA_PAGE
DBCC IND
もsys.dm_db_database_page_allocations
(WHERE句を使用)もインデックスページを報告せず、DBCC IND
のみが少なくとも1つのIAMページを報告します。
DATA_COMPRESSION:クラスタ化インデックスまたはヒープでROW
またはPAGE
圧縮を有効にしている場合は、これまでに述べたことのほとんどを忘れることができます。 96バイトのページヘッダー、行あたり2バイトのスロット配列、および行あたり14バイトのバージョン管理情報は引き続き存在しますが、データの物理表現は非常に複雑になります(圧縮時にすでに述べられているものよりもはるかに複雑になります)。は使用されていません)。たとえば、行圧縮を使用すると、SQL Serverは、各行ごとに各列に収まるように最小のコンテナーを使用しようとします。したがって、そうでなければ(BIGINT
も有効になっていないと仮定して)SPARSE
列がある場合、常に8バイトを使用します。値が-128から127の間(つまり、符号付き8ビット整数)の場合、1バイトだけを使用します。値がSMALLINT
に収まる場合、2バイトしか使用しません。 NULL
または0
の整数型はスペースをとらず、列をマッピングする配列でNULL
または "空"(つまり0
)として単に示されます。そして、他にもたくさんのルールがあります。 Unicodeデータを持っていますか(NCHAR
、NVARCHAR(1 - 4000)
、notNVARCHAR(MAX)
、たとえ行に格納されていても)? Unicode圧縮はSQL Server 2008 R2で追加されましたが、 rules の複雑さを考えると、実際の圧縮を行わずにすべての状況で「圧縮」値の結果を予測する方法はありません。
したがって、実際の2番目のクエリは、ディスクで使用される物理領域全体の点ではより正確ですが、クラスター化インデックスのREBUILD
を実行した場合にのみ正確になります。そしてその後も、100未満のFILLFACTOR
設定をすべて考慮する必要があります。それでも、常にページヘッダーがあり、多くの場合、小さすぎて行を収めることができないため単に埋めることができない「無駄な」スペースが十分にありますこのテーブル、または少なくとも論理的にそのスロットに入れる必要がある行。
「データ使用量」を決定する際の2番目のクエリの正確さに関して、ページヘッダーバイトはデータ使用量ではないため、バックアウトすることが最も公平であると考えられます。これらはビジネスコストのオーバーヘッドです。データページに1つの行があり、その行が単なるTINYINT
である場合、その1バイトは、データページが存在すること、したがってヘッダーの96バイトを必要としました。その1つの部門がデータページ全体に対して課金されますか?そのデータページが部門#2でいっぱいになった場合、「オーバーヘッド」コストを均等に分割するか、比例的に支払うのでしょうか。元に戻すのが最も簡単なようです。その場合、8
の値を使用してnumber of pages
を乗算するのは高すぎます。どうですか:
-- 8192 byte data page - 96 byte header = 8096 (approx) usable bytes.
SELECT 8060.0 / 1024 -- 7.906250
したがって、次のようなものを使用します。
(SUM(a.total_pages) * 7.91) / 1024 AS [TotalSpaceMB]
「number_of_pages」列に対するすべての計算。
[〜#〜] and [〜#〜]、フィールドごとにDATALENGTH
を使用しても行ごとのメタデータを返すことができないことを考慮して、DATALENGTH
を取得するテーブルごとのクエリに追加する必要があります各フィールドごとに、各「部門」でフィルタリングします。
ALLOW_SNAPSHOT_ISOLATION
またはREAD_COMMITTED_SNAPSHOT
がON
に設定されている場合)NULL
の場合はポインターが存在しないため、この部分は非常に不正確です。値が行に収まる場合は、ポインターよりもはるかに小さいか、非常に大きく、値が格納されます。 -行の場合、ポインターのサイズはデータの量に依存する場合があります。ただし、見積もり(つまり、「Swag」)が必要なだけなので、24バイトが適切な値のようです(まあ、他のすべてと同じです;-)。これは各MAX
フィールドです。したがって、次のようなものを使用します。
一般的に(行ヘッダー+列数+スロット配列+ NULLビットマップ):
([RowCount] * (( 4 + 2 + 2 + (1 + (({NumColumns} - 1) / 8) ))
一般に(「バージョン情報」が存在する場合は自動検出):
+ (SELECT CASE WHEN snapshot_isolation_state = 1 OR is_read_committed_snapshot_on = 1
THEN 14 ELSE 0 END FROM sys.databases WHERE [database_id] = DB_ID())
可変長の列がある場合は、次を追加します。
+ 2 + (2 * {NumVariableLengthColumns})
MAX
/LOB列がある場合は、以下を追加します。
+ (24 * {NumLobColumns})
一般に:
)) AS [MetaDataBytes]
これは正確ではなく、ヒープまたはクラスター化インデックスで行またはページの圧縮を有効にしている場合は機能しませんが、確実に近づけるはずです。
15%の違いの謎に関する更新
私たちも含めて、データページのレイアウトと、DATALENGTH
が2番目のクエリの確認に多くの時間を費やさなかったものをどのように説明できるかを考えることに集中しました。そのクエリを単一のテーブルに対して実行し、それらの値をsys.dm_db_database_page_allocations
によって報告されているものと比較しましたが、ページ数の値は同じではありませんでした。直感的に、集約関数とGROUP BY
を削除し、SELECT
リストをa.*, '---' AS [---], p.*
に置き換えました。そしてthenが明らかになりました:人々はこれらのあいまいなインターウェブのどこで情報とスクリプトを;-)から取得するかに注意する必要があります。質問に投稿された2番目のクエリは、特にこの特定の質問については、正確ではありません。
軽微な問題:それ以外に、GROUP BY rows
にはあまり意味がなく(その列が集計関数にない)、sys.allocation_units
とsys.partitions
の間のJOINは技術的に正しくありません。アロケーションユニットには3つのタイプがあり、そのうちの1つは別のフィールドにJOINする必要があります。多くの場合、partition_id
とhobt_id
は同じであるため、問題が発生することはありませんが、これら2つのフィールドの値が異なる場合があります。
主な問題:クエリはused_pages
フィールドを使用します。そのフィールドは、allページのタイプ(データ、インデックス、IAMなど)をカバーします。実際のデータのみを扱う場合に使用する別のより適切なフィールドがあります:data_pages
。
上記の項目を念頭に置き、ページヘッダーを取り消すデータページサイズを使用して、質問の2番目のクエリを調整しました。また、不要な2つのJOINを削除しました。sys.schemas
(SCHEMA_NAME()
への呼び出しに置き換えられます)とsys.indexes
(クラスター化インデックスは常にindex_id = 1
であり、index_id
にsys.partitions
があります)。
SELECT SCHEMA_NAME(st.[schema_id]) AS [SchemaName],
st.[name] AS [TableName],
SUM(sp.[rows]) AS [RowCount],
(SUM(sau.[total_pages]) * 8.0) / 1024 AS [TotalSpaceMB],
(SUM(CASE sau.[type]
WHEN 1 THEN sau.[data_pages]
ELSE (sau.[used_pages] - 1) -- back out the IAM page
END) * 7.91) / 1024 AS [TotalActualDataMB]
FROM sys.tables st
INNER JOIN sys.partitions sp
ON sp.[object_id] = st.[object_id]
INNER JOIN sys.allocation_units sau
ON ( sau.[type] = 1
AND sau.[container_id] = sp.[partition_id]) -- IN_ROW_DATA
OR ( sau.[type] = 2
AND sau.[container_id] = sp.[hobt_id]) -- LOB_DATA
OR ( sau.[type] = 3
AND sau.[container_id] = sp.[partition_id]) -- ROW_OVERFLOW_DATA
WHERE st.is_ms_shipped = 0
--AND sp.[object_id] = OBJECT_ID(N'dbo.table_name')
AND sp.[index_id] < 2 -- 1 = Clustered Index; 0 = Heap
GROUP BY SCHEMA_NAME(st.[schema_id]), st.[name]
ORDER BY [TotalSpaceMB] DESC;
多分これは不自然な答えですが、これは私が何をするかです。
したがって、DATALENGTHは全体の86%しか占めていません。それはまだ非常に代表的な分割です。 srutzkyからの優れた回答のオーバーヘッドはかなり均等に分割されているはずです。
合計には2番目のクエリ(ページ)を使用します。そして、最初の(データ長)を使用して分割を割り当てます。正規化を使用して多くのコストが割り当てられます。
そして、より近い答えはコストを上げることになるので、スプリットで負けた部門でさえもっと支払うかもしれないと考える必要があります。