Perché l'indice selettivo secondario non viene usato quando la clausola where filtra su `value ()`?


13

Impostare:

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 di esempio per ogni riga:

<Number>314</Number>

Il lavoro per la query è contare il numero di righe Tcon un valore specificato di <Number>.

Esistono due modi ovvi per farlo:

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;

Si scopre che value()e exists()richiede due diverse definizioni di percorso per il funzionamento dell'indice XML selettivo.

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

La sqlversione è per value()e la xqueryversione è per exist().

Potresti pensare che un indice del genere ti darebbe un piano con una buona ricerca ma gli indici XML selettivi sono implementati come una tabella di sistema con la chiave primaria Tcome chiave principale della chiave cluster della tabella di sistema. I percorsi specificati sono colonne sparse in quella tabella. Se si desidera un indice dei valori effettivi dei percorsi definiti, è necessario creare indici selettivi secondari, uno per ciascuna espressione di percorso.

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

Il piano di query per il exist()fa una ricerca nell'indice XML secondario seguito da una ricerca chiave nella tabella di sistema per l'indice XML selettivo (non so perché sia ​​necessario) e infine fa una ricerca Tper assicurarsi che ci siano effettivamente file lì dentro. L'ultima parte è necessaria perché non esiste alcun vincolo di chiave esterna tra la tabella di sistema e T.

inserisci qui la descrizione dell'immagine

Il piano per la value()query non è così bello. Esegue una scansione dell'indice cluster di Tcon un loop nidificato unito a una ricerca nella tabella interna per ottenere il valore dalla colonna sparsa e infine filtra il valore.

inserisci qui la descrizione dell'immagine

Se un indice selettivo deve essere utilizzato o meno viene deciso prima dell'ottimizzazione, ma se un indice selettivo secondario deve essere utilizzato o meno è una decisione basata sui costi dell'ottimizzatore.

Perché l'indice selettivo secondario non viene utilizzato quando la clausola where si attiva value()?

Aggiornare:

Le query sono semanticamente diverse. Se aggiungi una riga con il valore

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

la exist()versione conterebbe 2 righe e la values()query conterrebbe 1 riga. Ma con le definizioni dell'indice come sono specificate qui usando la singletondirettiva SQL Server ti impedirà di aggiungere una riga con più <Number>elementi.

Ciò tuttavia non ci consente di utilizzare la values()funzione senza specificare [1]per garantire al compilatore che avremo un solo valore. Questo [1]è il motivo per cui abbiamo un Top N Sort nel value()piano.

Sembra che sto chiudendo una risposta qui ...

Risposte:


11

La dichiarazione di singletonnell'espressione path dell'indice impone che non è possibile aggiungere più <Number>elementi ma il compilatore XQuery non lo prende in considerazione quando si interpreta l'espressione nella value()funzione. Devi specificare [1]per rendere felice SQL Server. L'uso dell'XML tipizzato con uno schema non aiuta neanche a questo. E a causa di ciò SQL Server crea una query che utilizza qualcosa che potrebbe essere chiamato un modello "applica".

La cosa più semplice da dimostrare è usare tabelle regolari invece di XML simulando la query che stiamo effettivamente eseguendo Te la tabella interna.

Ecco l'impostazione per la tabella interna come una tabella reale.

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;

Con entrambe le tabelle in atto è possibile eseguire l'equivalente della exist()query.

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

L'equivalente della value()query sarebbe simile a questo.

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;

L' top(1)e order by S.path_1_idè il colpevole ed è [1]nell'espressione Xpath che è la colpa.

Non penso che sia possibile per Microsoft risolvere questo problema con l'attuale struttura della tabella interna anche se ti fosse permesso di escludere [1]la values()funzione. Dovrebbero probabilmente creare più tabelle interne per ogni espressione di percorso con vincoli univoci in atto per garantire all'ottimizzatore che ci può essere solo un <number>elemento per ogni riga. Non sono sicuro che sarebbe effettivamente sufficiente per l'ottimizzatore di "uscire dal modello di applicazione".

Per te che lo pensi divertente e interessante e dato che stai ancora leggendo questo probabilmente lo sei.

Alcune query per esaminare la struttura della tabella interna.

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')
                     );
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.