Почему вторичный селективный индекс не используется, когда предложение 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]причине у нас есть Top N Sort в value()плане.

Похоже, я подхожу к ответу здесь ...

Микаэль Эрикссон
источник

Ответы:

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 , что виноват.

Я не думаю, что 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')
                     );
Микаэль Эрикссон
источник