当where子句对`value()`进行过滤时,为什么不使用二级选择索引?


13

设定:

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;

每行的样本XML:

<Number>314</Number>

查询的工作是计算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;

事实证明,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
);

sql版本是value()xquery版本是exist()

您可能会认为,像这样的索引将使您的计划具有很好的搜索效果,但是选择性的XML索引被实现为系统表,且主键T为系统表的集群键的前导键。指定的路径是该表中的稀疏列。如果要为已定义路径的实际值创建索引,则需要创建一个辅助选择索引,每个路径表达式一个。

create xml index SIX_T_pathSQL on dbo.T(XMLDoc)
  using xml index SIX_T for (pathSQL);

create xml index SIX_T_pathXQUERY on dbo.T(XMLDoc)
  using xml index SIX_T for (pathXQUERY);

用于查询的查询计划exist()在辅助XML索引中进行查找,然后在系统表中针对选择性XML索引进行键查找(不知道为什么需要这样做),最后进行查找T以确保实际上存在那里的行。最后一部分是必要的,因为系统表和之间没有外键约束T

在此处输入图片说明

value()查询的计划不是很好。它对T嵌套表进行聚簇索引扫描,并与内部表上的搜索连接,以从稀疏列中获取值,最后对值进行过滤。

在此处输入图片说明

在优化之前确定是否应该使用选择索引,但是是否应该使用辅助选择索引是优化器基于成本的决定。

为什么在where子句过滤时不使用二级选择索引value()

更新:

查询在语义上是不同的。如果您添加带有值的行

<Number>313</Number>
<Number>314</Number>` 

exist()版本将数2行和values()查询也要算1排。但是,使用此处指定的索引定义(使用singleton伪指令),SQL Server将阻止您添加包含多个<Number>元素的行。

但是,这不能让我们在values()未指定[1]保证编译器仅获得单个值的情况下使用该函数。这[1]就是我们在value()计划中进行前N个排序的原因。

看来我在这里接一个答案...

Answers:


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;

放置两个表后,您可以执行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
             );

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;

top(1)order by S.path_1_id是罪魁祸首,它是[1]在XPath表达式是罪魁祸首。

我认为,即使允许您[1]values()函数中删除,Microsoft也不可能用内部表的当前结构来解决此问题。他们可能必须为每个路径表达式创建多个内部表,并具有适当的约束条件,以确保优化器<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')
                     );
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.