web-dev-qa-db-ja.com

XMLがVARCHAR(MAX)より多くのストレージを使用するのはなぜですか?

XMLデータをvarchar(MAX)として格納する大きなテーブルがあります。データは参照/履歴の目的であり、照会されません。私が読んだ内容に基づいて、VARCHAR(MAX)の代わりにXMLデータ型として保存するとスペースが節約されるはずですが、テストではそれ以外の結果が示されています。以下を参照してください。t1_XMLのサイズはt1_NVARCHARMAXより小さく、t1_VARCHARMAXより大きくなっています。

set nocount on;

drop table t1_XML;
drop table t1_VARCHARMAX;
drop table t1_NVARCHARMAX;

create table t1_XML(col1 int identity primary key, col2 XML);
create table t1_VARCHARMAX(col1 int identity primary key, col2 varchar(max));
create table t1_NVARCHARMAX(col1 int identity primary key, col2 nvarchar(max));

go

declare @xml XML = '<root><element1>test</element1><element2>test</element2><element3>test</element3><element4>test</element4><element5>test</element5></root>'
    , @x int = 1;

while @x <= 10000
begin
    begin tran

    insert into dbo.t1_XML (col2) values (@xml);
    insert into dbo.t1_VARCHARMAX (col2) values (cast(@xml as varchar(max)));
    insert into dbo.t1_NVARCHARMAX (col2) values (cast(@xml as varchar(max)));

    commit tran

    set @x += 1;
end

exec sp_spaceused 'dbo.t1_XML';
exec sp_spaceused 'dbo.t1_VARCHARMAX';
exec sp_spaceused 'dbo.t1_NVARCHARMAX';

enter image description here

3
Greg

XMLデータ型について知っておくべきことが2つあります。これらは一緒に、発生していることを説明しています。

  1. @ EvanCarrollの回答 で述べたように、XMLデータ型が最適化されます。要素と属性の名前を繰り返すのではなく(通常はかなり繰り返され、XMLドキュメントが非常に大きくなることに不満を言う人が多いのはその理由の大部分です)、辞書/ルックアップリストが作成されます。数値IDを指定して、一意の名前を1回ずつ保存し、そのIDを使用してドキュメントの構造を設定します。これがXMLデータ型がXMLドキュメントを格納するためのより良い方法である理由です。
  2. さらに、XMLデータ型はUTF-16(リトルエンディアン)を使用して文字列値(要素名と属性名、および実際の文字列コンテンツの両方)を格納します。このデータ型は圧縮を使用しないため、文字列は基本的に1文字あたり2バイトまたは4バイトであり、ほとんどの文字は2バイトです。

使用している特定のテストXMLドキュメントとVARCHARデータ型(1文字あたり1〜2バイト、多くの場合1バイトの種類)を見て、結果として何が表示されているかを説明できます:

  1. 各要素(rootelement1など)は1回だけ使用されるため、名前をルックアップリストに配置する唯一の節約は、サイズを正確に半分に削減することです。ただし、XMLタイプはUTF-16を使用するため、各文字列のサイズは2倍になり、要素名をルックアップリストに移動することによる節約をキャンセルできます。この時点で、ドキュメント構造(つまり、要素名)のみを見ている場合、XMLタイプとVARCHARバージョンの間に実質的な違いはないはずです。
  2. ただし、各要素(test)の文字列コンテンツは、バイト数の2倍になります。XMLの4バイトではなく、VARCHARの8バイトです。行ごとに「test」のインスタンスが5つあるとすると、XMLタイプの行ごとに20バイトが追加されます。 1万行の場合、600,000バイトの差から200,000バイトが追加されます。残りは、XMLタイプの内部オーバーヘッドと、各行がわずかに大きいために同じ数の行を格納するために必要な追加のデータページ数の追加のページオーバーヘッドです。

この動作をわかりやすく説明するために、XMLデータの次の2つのバリエーションを検討してください。1つ目は質問とまったく同じXML、2つ目はほとんど同じですが、すべての要素が同じ名前です。 2番目のバージョンでは、すべての要素名が「element1」であるため、元のバージョンの各要素と同じ長さになります。この結果、VARCHARデータ長はどちらの場合も同じになります。ただし、2番目のバージョンで同じ要素名を使用すると、内部の最適化をより目立たせることができます。

-- Original XML (unique element names -- "element1", "element2", ... "elementN"):
DECLARE @xml XML =  '<root><element1>test</element1><element2>test</element2>
<element3>test</element3><element4>test</element4><element5>test</element5></root>';
SELECT DATALENGTH(@xml) AS [XmlBytes],
       DATALENGTH(CONVERT(VARCHAR(MAX), @xml)) AS [VarcharBytes];

-- More "typical" XML (repeated element names -- all "element1"):
DECLARE @xml2 XML = '<root><element1>test</element1><element1>test</element1>
<element1>test</element1><element1>test</element1><element1>test</element1></root>';
SELECT DATALENGTH(@xml2) AS [XmlBytes],
       DATALENGTH(CONVERT(VARCHAR(MAX), @xml2)) AS [VarcharBytes];

結果:

ElementNames    XmlBytes    VarcharBytes
------------    --------    ------------
Unique          197         138
Non-Unique      109         138
6
Solomon Rutzky

XMLデータ型と列(SQL Server)のドキュメントから

データは、データのXMLコンテンツを保持する内部表現に格納されます。この内部表現には、包含階層、ドキュメントの順序、要素と属性の値に関する情報が含まれます。具体的には、XMLデータのインフォセットの内容が保持されます。インフォセットの詳細については、 http://www.w3.org/TR/xml-infoset にアクセスしてください。次の情報が保持されないため、インフォセットのコンテンツはテキストXMLの同一のコピーではない可能性があります:意味のない空白、属性の順序、名前空間プレフィックス、およびXML宣言

_binary_representation_size_はおおよそdata + _information about the containment hierarchy, document order, and element and attribute values_-_insignificant white spaces, order of attributes, namespace prefixes, and XML declaration_

名前空間の接頭辞がなく、余計にデータを格納しているだけの空白がある場合、それは明らかな勝利ではありません。

保存だけを行い、機能や検証を気にしない場合は、ドキュメントでnvarchar(max)を使用することを明示的に示しています。

これらの条件が満たされていない場合(高度な機能が必要)、リレーショナルデータモデルを使用する必要があります。たとえば、データがXML形式であるが、アプリケーションがデータベースを使用してデータを格納および取得する場合、[n]varchar(max)列だけで十分です。 XML列にデータを格納すると、さらに利点があります。これには、データが整形式または有効であるとエンジンが判断することも含まれます。また、きめ細かいクエリとXMLデータへの更新のサポートも含まれます。

2
Evan Carroll

SQL Server 2016は [〜#〜] compress [〜#〜] 関数を導入しました。これを@Solomonの例に適用すると:

... DATALENGTH(COMPRESS(CONVERT(VARCHAR(MAX), @xml))) AS [VarcharCompressed];

... DATALENGTH(COMPRESS(CONVERT(VARCHAR(MAX), @xml2))) AS [VarcharCompressed];

さらにスペースを節約できます:

ElementNames    XmlBytes    VarcharBytes  VarcharCompressed
------------    --------    ------------  -----------------
Unique          197         138           72
Non-Unique      109         138           49

一意の要素名と繰り返される要素名の両方でスペースが節約されることは注目に値します。

0
Michael Green