Mik*_*son 13 performance xml sql-server execution-plan sql-server-2012 query-performance
设置:
create table dbo.T
(
ID int identity primary key,
XMLDoc xml not null
);
insert into dbo.T(XMLDoc)
select (
select N.Number
for xml path(''), type
)
from (
select top(10000) row_number() over(order by (select null)) as Number
from sys.columns as c1, sys.columns as c2
) as N;
Run Code Online (Sandbox Code Playgroud)
每行的示例 XML:
<Number>314</Number>
Run Code Online (Sandbox Code Playgroud)
查询的任务是计算T指定值为 的行数<Number>。
有两种明显的方法可以做到这一点:
select count(*)
from dbo.T as T
where T.XMLDoc.value('/Number[1]', 'int') = 314;
select count(*)
from dbo.T as T
where T.XMLDoc.exist('/Number[. eq 314]') = 1;
Run Code Online (Sandbox Code Playgroud)
事实证明,value()并且exists()需要选择性XML索引工作两种不同的路径定义。
create selective xml index SIX_T on dbo.T(XMLDoc) for
(
pathSQL = '/Number' as sql int singleton,
pathXQUERY = '/Number' as xquery 'xs:double' singleton
);
Run Code Online (Sandbox Code Playgroud)
该sql版本是value()与xquery版本是exist()。
您可能认为这样的索引会给您一个很好的查找计划,但选择性 XML 索引是作为系统表实现的,其主键是T作为系统表聚集键的前导键。指定的路径是该表中的稀疏列。如果您想要定义路径的实际值的索引,您需要创建二级选择性索引,每个路径表达式一个。
select count(*)
from dbo.T as T
where T.XMLDoc.value('/Number[1]', 'int') = 314;
select count(*)
from dbo.T as T
where T.XMLDoc.exist('/Number[. eq 314]') = 1;
Run Code Online (Sandbox Code Playgroud)
查询计划exist()在二级 XML 索引中进行搜索,然后在系统表中对选择性 XML 索引进行键查找(不知道为什么需要这样做),最后它进行查找T以确保确实存在行在那里。最后一部分是必需的,因为系统表和T.

value()查询计划不太好。它T使用嵌套循环连接对内部表上的查找进行聚集索引扫描,以从稀疏列中获取值,并最终过滤该值。

是否应该使用选择性索引是在优化之前决定的,但是否应该使用二级选择性索引是优化器基于成本的决定。
为什么 where 子句过滤时不使用二级选择性索引value()?
更新:
查询在语义上是不同的。如果添加具有值的行
create selective xml index SIX_T on dbo.T(XMLDoc) for
(
pathSQL = '/Number' as sql int singleton,
pathXQUERY = '/Number' as xquery 'xs:double' singleton
);
Run Code Online (Sandbox Code Playgroud)
该exist()版本将计算 2 行,values()查询将计算 1 行。但是,使用此处指定的索引定义,使用singletonSQL Server 指令将阻止您添加具有多个<Number>元素的行。
然而,这不允许我们在values()没有指定的情况下使用该函数[1]来保证编译器我们只会得到一个值。这[1]就是我们在value()计划中设置 Top N Sort 的原因。
看起来我在这里接近答案......
Mik*_*son 11
singleton索引的路径表达式中的声明强制您不能添加多个<Number>元素,但 XQuery 编译器在解释value()函数中的表达式时不会考虑这一点。您必须指定[1]以使 SQL Server 满意。使用带有模式的类型化 XML 也无济于事。正因为如此,SQL Server 构建了一个使用可称为“应用”模式的东西的查询。
最容易演示的是使用常规表而不是 XML 模拟我们实际执行的查询T和内部表。
这是将内部表设置为真实表的设置。
create table dbo.xml_sxi_table
(
pk1 int not null,
row_id int,
path_1_id varbinary(900),
pathSQL_1_sql_value int,
pathXQUERY_2_value float
);
go
create clustered index SIX_T on xml_sxi_table(pk1, row_id);
create nonclustered index SIX_pathSQL on xml_sxi_table(pathSQL_1_sql_value) where path_1_id is not null;
create nonclustered index SIX_T_pathXQUERY on xml_sxi_table(pathXQUERY_2_value) where path_1_id is not null;
go
insert into dbo.xml_sxi_table(pk1, row_id, path_1_id, pathSQL_1_sql_value, pathXQUERY_2_value)
select T.ID, 1, T.ID, T.ID, T.ID
from dbo.T;
Run Code Online (Sandbox Code Playgroud)
有了这两个表,您就可以执行等效的exist()查询。
select count(*)
from dbo.T
where exists (
select *
from dbo.xml_sxi_table as S
where S.pk1 = T.ID and
S.pathXQUERY_2_value = 314 and
S.path_1_id is not null
);
Run Code Online (Sandbox Code Playgroud)
value()查询的等价物看起来像这样。
select count(*)
from dbo.T
where (
select top(1) S.pathSQL_1_sql_value
from dbo.xml_sxi_table as S
where S.pk1 = T.ID and
S.path_1_id is not null
order by S.path_1_id
) = 314;
Run Code Online (Sandbox Code Playgroud)
的top(1)和order by S.path_1_id是罪魁祸首,它是[1]在XPath表达式是罪魁祸首。
我认为 Microsoft 不可能用内部表的当前结构来解决这个问题,即使你被允许[1]从values()函数中删除。他们可能必须为每个路径表达式创建多个内部表,并带有唯一的约束,以保证优化器<number>每行只能有一个元素。不确定这实际上是否足以让优化器“打破应用模式”。
对于那些认为这很有趣和有趣的人来说,因为你还在阅读这篇文章,所以你可能是。
一些查询来看看内部表的结构。
select T.name,
T.internal_type_desc,
object_name(T.parent_id) as parent_table_name
from sys.internal_tables as T
where T.parent_id = object_id('T');
select C.name as column_name,
C.column_id,
T.name as type_name,
C.max_length,
C.is_sparse,
C.is_nullable
from sys.columns as C
inner join sys.types as T
on C.user_type_id = T.user_type_id
where C.object_id in (
select T.object_id
from sys.internal_tables as T
where T.parent_id = object_id('T')
)
order by C.column_id;
select I.name as index_name,
I.type_desc,
I.is_unique,
I.filter_definition,
IC.key_ordinal,
C.name as column_name,
C.column_id,
T.name as type_name,
C.max_length,
I.is_unique,
I.is_unique_constraint
from sys.indexes as I
inner join sys.index_columns as IC
on I.object_id = IC.object_id and
I.index_id = IC.index_id
inner join sys.columns as C
on IC.column_id = C.column_id and
IC.object_id = C.object_id
inner join sys.types as T
on C.user_type_id = T.user_type_id
where I.object_id in (
select T.object_id
from sys.internal_tables as T
where T.parent_id = object_id('T')
);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
674 次 |
| 最近记录: |