Mengapa indeks selektif sekunder tidak digunakan ketika klausa mana memfilter pada `value ()`?

13

Mempersiapkan:

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;

Contoh XML untuk setiap baris:

<Number>314</Number>

Pekerjaan untuk kueri adalah menghitung jumlah baris Tdengan nilai yang ditentukan <Number>.

Ada dua cara yang jelas untuk melakukan ini:

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;

Ternyata itu value()dan exists()membutuhkan dua definisi jalur yang berbeda untuk indeks XML selektif untuk bekerja.

create selective xml index SIX_T on dbo.T(XMLDoc) for
(
  pathSQL = '/Number' as sql int singleton,
  pathXQUERY = '/Number' as xquery 'xs:double' singleton
);

The sqlversi untuk value()dan xqueryversi untuk exist().

Anda mungkin berpikir bahwa indeks seperti itu akan memberi Anda rencana dengan pencarian yang bagus tetapi indeks XML selektif diimplementasikan sebagai tabel sistem dengan kunci utama Tsebagai kunci utama dari kunci berkerumun dari tabel sistem. Jalur yang ditentukan adalah kolom jarang di tabel itu. Jika Anda menginginkan indeks nilai aktual jalur yang ditentukan, Anda perlu membuat indeks selektif sekunder, satu untuk setiap ekspresi jalur.

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);

Rencana kueri untuk exist()pencarian di indeks XML sekunder diikuti oleh pencarian kunci dalam tabel sistem untuk indeks XML selektif (tidak tahu mengapa itu diperlukan) dan akhirnya melakukan pencarian Tuntuk memastikan benar-benar ada baris di sana. Bagian terakhir diperlukan karena tidak ada batasan kunci asing antara tabel sistem dan T.

masukkan deskripsi gambar di sini

Rencana value()kueri tidak begitu bagus. Itu melakukan pemindaian indeks berkerumun Tdengan loop bersarang bergabung melawan pencarian di tabel internal untuk mendapatkan nilai dari kolom jarang dan akhirnya menyaring nilai.

masukkan deskripsi gambar di sini

Jika indeks selektif harus digunakan atau tidak diputuskan sebelum optimasi tetapi jika indeks selektif sekunder harus digunakan atau tidak adalah keputusan berdasarkan biaya oleh pengoptimal.

Mengapa indeks selektif sekunder tidak digunakan ketika klausa mana menyaring value()?

Memperbarui:

Pertanyaannya secara semantik berbeda. Jika Anda menambahkan baris dengan nilai

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

yang exist()versi akan menghitung 2 baris dan values()permintaan akan menghitung 1 baris. Tetapi dengan definisi indeks seperti yang ditentukan di sini menggunakan singletondirektif SQL Server akan mencegah Anda menambahkan baris dengan banyak <Number>elemen.

Namun itu tidak memungkinkan kita menggunakan values()fungsi tanpa menentukan [1]untuk menjamin kompiler bahwa kita hanya akan mendapatkan nilai tunggal. Itulah [1]alasan kami memiliki Urut N Teratas dalam value()rencana.

Sepertinya saya mendekati jawaban di sini ...

Mikael Eriksson
sumber

Jawaban:

11

Deklarasi singletondalam path path dari indeks memberlakukan bahwa Anda tidak dapat menambahkan beberapa <Number>elemen tetapi kompiler XQuery tidak mempertimbangkannya ketika menafsirkan ekspresi dalam value()fungsi. Anda harus menentukan[1] untuk membuat SQL Server bahagia. Menggunakan XML yang diketik dengan skema juga tidak membantu. Dan karena itu SQL Server membangun kueri yang menggunakan sesuatu yang bisa disebut pola "apply".

Cara paling mudah untuk didemonstrasikan adalah dengan menggunakan tabel biasa alih-alih XML yang mensimulasikan kueri yang sebenarnya kami jalankan Tdan tabel internal.

Berikut adalah pengaturan untuk tabel internal sebagai tabel sebenarnya.

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;

Dengan kedua tabel di tempat Anda dapat menjalankan setara dengan exist()kueri.

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
             );

Setara dengan value()kueri akan terlihat seperti ini.

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;

The top(1)dan order by S.path_1_idadalah pelakunya dan [1]dalam ekspresi Xpath yang harus disalahkan.

Saya tidak berpikir itu mungkin bagi Microsoft untuk memperbaikinya dengan struktur tabel internal saat ini bahkan jika Anda diizinkan untuk meninggalkan [1]dari values()fungsi. Mereka mungkin harus membuat beberapa tabel internal untuk setiap ekspresi jalur dengan batasan unik untuk menjamin optimizer bahwa hanya ada satu <number>elemen untuk setiap baris. Tidak yakin itu akan cukup bagi pengoptimal untuk "keluar dari pola yang berlaku".

Bagi Anda yang menganggap ini menyenangkan dan menarik dan karena Anda masih membaca ini, Anda mungkin sedang.

Beberapa pertanyaan untuk melihat struktur tabel internal.

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')
                     );
Mikael Eriksson
sumber