为什么 XML 比 VARCHAR(MAX) 占用更多的存储空间?

Gre*_*reg 3 xml sql-server datatypes sql-server-2012

我们有将 XML 数据存储为 varchar(MAX) 的大表。数据仅供参考/历史用途,不作查询。根据我所读到的内容,存储为 XML 数据类型而不是 VARCHAR(MAX) 应该可以节省空间,但我的测试显示并非如此。见下文,其中 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';
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

Sol*_*zky 6

关于XML数据类型,有两件事需要了解,它们共同解释了您所遇到的情况:

  1. @EvanCarroll 的回答所述,XML数据类型已优化。意思是,而不是重复元素和属性名称(通常会重复很多,这也是为什么很多人(有时理所当然地)抱怨 XML 文档如此庞大的原因的很大一部分),而是创建了一个字典/查找列表来将每个唯一名称存储一次,给定一个数字 ID,该 ID 用于填充文档的结构。这就是为什么XML数据类型通常是存储 XML 文档的更好方法的原因。
  2. 此外,该XML数据类型使用 UTF-16(Little Endian)来存储字符串值(元素和属性名称以及任何实际的字符串内容)。此数据类型不使用压缩,因此字符串基本上是每个字符 2 或 4 个字节,其中大多数字符是 2 字节类型。

查看您正在使用的特定测试 XML 文档和VARCHAR数据类型(每个字符 1 到 2 个字节,最常见的是 1 字节类型),我们现在可以解释您所看到的结果:

  1. 您的每个元素(rootelement1等)仅使用一次,因此将名称放入查找列表的唯一节省是将大小正好减少一半。但是,XML 类型使用 UTF-16,因此每个字符串的大小是原来的两倍,抵消了将元素名称移动到查找列表中的节省。在这一点上,如果只看文档结构(即元素名称),那么XML类型和VARCHAR版本之间实际上应该没有区别。
  2. 但是,每个元素(即test)中的字符串内容占用的字节数是 8 个字节,XML而不是 4 个字节VARCHAR。鉴于每行有 5 个“测试”实例,即该XML类型每行 20 个额外字节。在 10k 行时,即 600,000 字节差异中有 200,000 个额外字节。其余的是XML类型的内部开销和由于每行稍大而需要存储相同行数所需的额外数据页数的额外页开销。

为了更好地说明这种行为,请考虑 XML 数据的以下两个变体:第一个与问题中的 XML 完全相同,第二个几乎相同,但所有元素都具有相同的名称。在第二个版本中,所有元素名称都是“element1”,因此它们与原始版本中的每个元素的长度相同。这导致VARCHAR两种情况下的数据长度相同。但是在第二个版本中元素名称相同使得内部优化更加明显。

-- 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];
Run Code Online (Sandbox Code Playgroud)

结果:

ElementNames    XmlBytes    VarcharBytes
------------    --------    ------------
Unique          197         138
Non-Unique      109         138
Run Code Online (Sandbox Code Playgroud)