Mid*_*one 9 performance xml sql-server t-sql index-tuning
我已经设置了一个测试来对 SQL 服务器中的 xml 性能进行基准测试。
测试设置
表和索引设计
CREATE TABLE [dbo].[xml_Test]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[GUID] [varchar](50) NULL,
[JSON_Data] [varchar](max) NULL,
[XML_Data] [xml] NULL,
CONSTRAINT [PK_xml_Test] PRIMARY KEY CLUSTERED ([ID] ASC)
);
ALTER TABLE [dbo].[xml_Test] ADD CONSTRAINT [DF_xml_Test_GUID] DEFAULT (newid()) FOR [GUID];
ALTER TABLE [dbo].[xml_Test] ADD CONSTRAINT [PK_xml_Test] PRIMARY KEY CLUSTERED ([ID] ASC);
CREATE PRIMARY XML INDEX [PK_xml] ON [dbo].[xml_Test]
( [XML_Data]);
CREATE XML INDEX [IX_xml_Path] ON [dbo].[xml_Test]
( [XML_Data])
USING XML INDEX [PK_xml] FOR PATH;
Run Code Online (Sandbox Code Playgroud)
示例 XML 架构
<data>
<id>3812</id>
<guid>E3735046-1183-4A79-B8EE-806312B533D6",</guid>
<firstName>John</firstName>
<lastName>Doe</lastName>
<tel>123-123-1234</tel>
<city>Toronto</city>
<prov>Ontario</prov>
<Q.49.R.47>14325</Q.49.R.47>
<Q.1>14326</Q.1>
<Q.9>143257</Q.9>
<Q.25>14328</Q.25>
<Q.50>14329</Q.50>
<Q.51>14330</Q.51>
<Q.30>14331</Q.30>
<Q.22>14332</Q.22>
<Q.100>14333</Q.100>
<Q.70.R.4>1</Q.70.R.4>
<Q.43>14335</Q.43>
<Q.3>14336</Q.3>
<Q.84.R.21.L.19>1</Q.84.R.21.L.19>
<done>1</done>
</data>
Run Code Online (Sandbox Code Playgroud)
当我使用两个不同的值去查询表时,我得到了一个非常有效的搜索结果和一个扫描需要更长的数量级才能完成的另一个结果。
我不确定为什么第二个查询无法对索引进行查找。我认为这可能与开始时不在索引中的元素有关,但即使如此,我也不知道为什么需要扫描,除非索引确实没有索引所有路径。
查询 A
select ID, 'Query A'
from xml_test
where XML_Data.exist('(/data/Q.70.R.4)[1]')=1
Run Code Online (Sandbox Code Playgroud)
查询 B
select id, 'Query B'
from xml_test
where XML_Data.exist('(/data/Q.61.R.15)[1]')=1
Run Code Online (Sandbox Code Playgroud)
如您所见,查询 A 的执行时间为 5 毫秒,而查询 B 的执行时间几乎为 15 秒。
相同的查询格式但不同的输入会产生如此明显低效的执行计划的原因是什么?
在旁边
SQL Server 中的 XML 索引是作为内部表实现的,它是节点表的持久版本,与 XML 分解函数生成的非常相似。
内部表中的一列称为 hid,该列包含在查找路径表达式时使用的值。该值是一个ordpath值。当您创建路径 xml 索引时,您将获得一个以 hid 作为前导列的非聚集索引。
当 SQL Server 向表中插入 XML 数据时,会生成特定元素名称的 ordpath。为了对路径进行编码,将连接不同的 ordpath 值。
简化后的路径 X/Y/Z 可以表示为 1.2.3,Y/Z/Z 可以表示为 2.3.3。
SQL Server 必须有办法为相同的元素名称生成相同的 ordpath 值。这可以通过某种字典或表来完成,这些字典或表跟踪一个 XML 索引的所有生成的 ordpath 值。我在这里有点模糊,因为我没有在任何地方找到任何说明这是如何完成的,我也没有找到它的存储位置。
使用此 XML 插入一行
<X>
<Y>
<Z />
</Y>
</X>
Run Code Online (Sandbox Code Playgroud)
将在内部表中为您提供此内容,并添加一列额外的列以显示以 hid 编码的路径表达式
id hid pk1
0x 1
0x58  1 Z
0x5AC0 Á€À€ 1 Y/Z
0x5AD6 €Á€À€ 1 X/Y/Z
Run Code Online (Sandbox Code Playgroud)
您可以使用 DAC 登录查看内部表。
当您查询路径表达式时会发生什么(在这里我再次有点推测)是用于确保具有相同名称的元素获得相同 ordpath 值的相同字典/查找表用于查找 hid用于内部表中的索引查找。XQuery 中的路径表达式始终是静态的,因此用于查询的 hid 值可以存储在查询计划中并重新使用。
对存在的节点的查询得到一个查询,在 hid 上查找,其中Î要搜索的 hid 值。
<SeekKeys>
<Prefix ScanType="EQ">
<RangeColumns>
<ColumnReference Database="[yy]" Schema="[sys]" Table="[xml_index_nodes_658101385_256000]"
Alias="[NodeThatExist:1]" Column="hid" />
</RangeColumns>
<RangeExpressions>
<ScalarOperator ScalarString="'Î'">
<Const ConstValue="'Î'" />
</ScalarOperator>
</RangeExpressions>
</Prefix>
</SeekKeys>
Run Code Online (Sandbox Code Playgroud)
对不存在的节点的查询为您提供了一种非常不同的行定位方式。
select count(*)
from dbo.T
where T.X.exist('NodeDoesNotExist') = 1
Run Code Online (Sandbox Code Playgroud)
计算标量使用一些函数来计算过滤运算符中检查的值。
我在这里所做的一些测试可以在这里找到。请注意,您实际上并不需要为索引中的元素生成 hid 来获取搜索计划,您只需尝试插入它即可。
作为旁注,查询的重写以使用一种方式或另一种方式来查找 XML 查询的行是在查询优化器开始执行其工作之前完成的,因此,如果您获得了搜索,也取决于隐藏内部表中的列。
Paul White 在聊天中提到了一些关于插入的元素名称的存储位置:
QNames 存储在 sys.sysqnames 中。XML algebrizer 需要现有的已知 QName 来生成优化的路径转换。即使用路径索引。将条目添加到系统事务中的 sys.sysqnames 中,不会感觉到用户事务
如果可以,请考虑以更常用的格式存储您的 xml。这可能需要在流程的早期阶段进行更改,或者在导入数据时进行一些预处理,但这很值得。
关键观察是元素名称中的编码信息非常不寻常。使用包含可变数据的可预测结构(理想情况下符合模式)的 xml 通常是一个更好的主意。
为了说明这一点,我已将示例数据重组为以下更一般的项目和价值组织:
CREATE TABLE [dbo].[xml_Test]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[GUID] [varchar](50) NULL DEFAULT NEWID(),
[JSON_Data] [varchar](max) NULL,
[XML_Data] [xml] NULL,
CONSTRAINT [PK_xml_Test] PRIMARY KEY CLUSTERED ([ID] ASC)
);
INSERT TOP (10000)
dbo.xml_Test (XML_Data)
SELECT
CONVERT(xml,
N'
<data>
<id>3812</id>
<guid>E3735046-1183-4A79-B8EE-806312B533D6</guid>
<firstName>John</firstName>
<lastName>Doe</lastName>
<tel>123-123-1234</tel>
<city>Toronto</city>
<prov>Ontario</prov>
<qitems>
<item><name>Q.49.R.47</name><value>14325</value></item>
<item><name>Q.1</name><value>14326</value></item>
<item><name>Q.9</name><value>14357</value></item>
<item><name>Q.25</name><value>14328</value></item>
<item><name>Q.50</name><value>14329</value></item>
<item><name>Q.51</name><value>14330</value></item>
<item><name>Q.30</name><value>14331</value></item>
<item><name>Q.22</name><value>14332</value></item>
<item><name>Q.100</name><value>14333</value></item>
<item><name>Q.70.R.4.1</name><value>1</value></item>
<item><name>Q.43</name><value>14335</value></item>
<item><name>Q.3</name><value>14336</value></item>
<item><name>Q.84.R.21.L.19</name><value>1</value></item>
</qitems>
<done>1</done>
</data>
')
FROM sys.all_columns AS AC1
CROSS JOIN sys.all_columns AS AC2;
Run Code Online (Sandbox Code Playgroud)
我碰巧使用了以元素为中心的设计,但如果您愿意,也可以在item元素上同样使用name和value属性。
添加一个具有不同 Q 名称(Q.999)的 xml 实例:
INSERT dbo.xml_Test (XML_Data)
SELECT
CONVERT(xml,
N'
<data>
<id>3812</id>
<guid>E3735046-1183-4A79-B8EE-806312B533D6</guid>
<firstName>John</firstName>
<lastName>Doe</lastName>
<tel>123-123-1234</tel>
<city>Toronto</city>
<prov>Ontario</prov>
<qitems>
<item><name>Q.999</name><value>-999</value></item>
</qitems>
<done>1</done>
</data>
');
Run Code Online (Sandbox Code Playgroud)
重新组织的结构适用于选择性 xml 索引:
CREATE SELECTIVE XML INDEX sxi
ON dbo.xml_Test (XML_Data)
FOR
(
qitem_path = '/data/qitems/item' AS XQUERY 'node()',
qitem_name = '/data/qitems/item/name' AS XQUERY 'xs:string' MAXLENGTH(20) SINGLETON
--qitem_value = '/data/qitems/item/value' AS SQL integer SINGLETON,
);
-- Optional secondary index for name
CREATE XML INDEX sxi_qitem_name
ON dbo.xml_Test (XML_Data)
USING XML INDEX sxi
FOR (qitem_name);
Run Code Online (Sandbox Code Playgroud)
如果您希望索引对提取值有用,请取消注释值路径。
定位 Q.999 名称现在非常有效:
SELECT
--XT.XML_Data.value('(./data/qitems/item/value)[1]', 'integer'),
XT.ID
FROM dbo.xml_Test AS XT
WHERE
XT.XML_Data.exist('./data/qitems/item[name="Q.999"]') = 1;
Run Code Online (Sandbox Code Playgroud)
搜索不存在的名称 (Q.61.R.15) 同样有效:
SELECT
--XT.XML_Data.value('(./data/qitems/item/value)[1]', 'integer'),
XT.ID
FROM dbo.xml_Test AS XT
WHERE
XT.XML_Data.exist('./data/qitems/item[name="Q.61.R.15"]') = 1;
Run Code Online (Sandbox Code Playgroud)
NOT NULL如果合适,您可以通过创建 xml column 来避免其中的一个连接。
| 归档时间: |
|
| 查看次数: |
226 次 |
| 最近记录: |