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>

これを行うには、2つの明白な方法があります。

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のクラスターキーの主キーとしてのプライマリキーを持つシステムテーブルとして実装されます。指定されたパスは、そのテーブルのスパース列です。定義されたパスの実際の値のインデックスが必要な場合は、各パス式に1つずつ、セカンダリ選択インデックスを作成する必要があります。

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行をカウントします。ただし、singletonSQL Serverディレクティブを使用してインデックス定義をここで指定すると、複数の<Number>要素を持つ行を追加できなくなります。

ただし、コンパイラーが単一の値のみを取得することを保証values()する[1]ように指定せずに関数を使用することはできません。これ[1]が、value()プランにトップNソートがある理由です。

ここで答えに近づいているように見えます...

回答:


11

singletonインデックスのパス式での宣言は、複数の<Number>要素を追加できないことを強制しますが、XQueryコンパイラはvalue()関数内の式を解釈するときにそれを考慮しません。[1]SQL Serverを満足させるには、指定する必要があります。型付きXMLをスキーマで使用しても、それは役に立ちません。そのため、SQL Serverは、 "適用"パターンと呼ばれるものを使用するクエリを作成します。

実際に実行するクエリTと内部テーブルをシミュレートするXMLの代わりに、通常のテーブルを使用するのが最も簡単です。

以下は、実際のテーブルとしての内部テーブルのセットアップです。

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]から除外することを許可されていたとしても、Microsoftが内部テーブルの現在の構造でこれを修正することは不可能だと思いvalues()ます。おそらく<number>、各行に1つの要素しか存在できないことをオプティマイザーに保証するために、一意の制約が設定された各パス式に対して複数の内部テーブルを作成する必要があります。オプティマイザが「適用パターンから抜け出す」ために実際に十分であるかどうかはわかりません。

この楽しさと面白いと思う人のために、あなたはまだこれを読んでいるので、おそらくあなたです。

内部テーブルの構造を調べるためのクエリ。

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')
                     );
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.