Чому вторинний селективний індекс не використовується, коли пункт фільтрує на "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з вкладеними петлями, об'єднаними проти пошуку на внутрішній таблиці, щоб отримати значення з розрідженого стовпця і, нарешті, фільтрує значення.

введіть тут опис зображення

Якщо перед оптимізацією слід використовувати вибірковий індекс чи не вирішувати, але якщо використовується вторинний селективний індекс чи ні, це оптимізатор на основі витрат.

Чому вторинний селективний індекс не використовується, коли пункт фільтр включений value()?

Оновлення:

Запити семантично різні. Якщо ви додаєте рядок зі значенням

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

exist()версія буде розраховувати 2 рядки і values()запит буде розраховувати 1 рядок. Але з визначеннями індексу, як вони вказані тут, за допомогою singletonдирективи SQL Server не дасть вам додати рядок з декількома <Number>елементами.

Однак це не дозволяє нам використовувати values()функцію, не вказуючи [1]гарантії компілятора, що ми отримаємо лише одне значення. Тому [1]ми value()плануємо сортувати верхній N.

Схоже, я закриваюсь у відповіді тут ...

Відповіді:


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')
                     );
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.