2015-06-09 13 views
5

Sto esplorando i modi per migliorare le prestazioni di un'applicazione che posso influire solo a livello di database in misura limitata. La versione di SQL Server è il 2012 SP2 e la struttura della tabella e la vista in questione è (non posso influire realmente presente + notare che il documento XML può avere diverse centinaia di elementi in totale):Server Sql: l'indice XML selettiva non viene utilizzato in modo efficiente

CREATE TABLE Orders(
    id nvarchar(64) NOT NULL, 
    xmldoc xml NULL, 
    CONSTRAINT PK_Order_id PRIMARY KEY CLUSTERED (id) 
); 

CREATE VIEW V_Orders as 
SELECT 
    a.id, a.xmldoc 
    ,a.xmldoc.value('data(/row/c1)[1]', 'nvarchar(max)') "Stuff" 
    ,a.xmldoc.value('data(/row/c2)[1]', 'nvarchar(max)') "OrderType" 
etc..... many columns 
from Orders a; 

Una query tipica (e la uno utilizzato per la prova di seguito):

SELECT id FROM V_Orders WHERE OrderType = '30791' 

Tutte le query vengono eseguite sulla vista e posso influenzare né le query né la struttura della tabella/vista.

ho pensato di aggiungere un indice XML selettivo al tavolo sarebbe il mio salvatore:

CREATE SELECTIVE XML INDEX I_Orders_OrderType ON Orders(xmldoc) 
FOR(
    pathOrderType = '/row/c2' as SQL [nvarchar](20) 
) 

Ma anche dopo l'aggiornamento delle statistiche il piano di esecuzione è alla ricerca strano. Impossibile pubblicare un'immagine come nuovo account, quindi i dettagli rilevanti come testo:

  • Ricerca di indice clusterizzata da selectiveXml (costo: 2% del totale). Numero previsto di righe 1 ma il numero atteso di tempi di esecuzione 1269 (numero di righe della tabella)
  • -> Top N sort (Costo: 95% del totale)
  • -> Calcola scalare (costo 0)

  • Filiale separata: scansione indice cluster PK_Order_id (costo: 3% del totale). Numero previsto di righe 1269

  • -> fuse per i risultati del computer scalari con cicli annidati (a sinistra outer join)
  • -> Filtro
  • -> Risultato finale (numero previsto di righe 1269)

In realtà con i miei dati di test la query non restituisce alcun risultato, ma se restituisce uno o pochi non fa alcuna differenza. I tempi di esecuzione supportano la query in realtà impiegando tutto il tempo che è possibile dedurre dal piano di esecuzione e hanno un conteggio delle migliaia di volte.

Quindi la mia domanda è: perché l'indice xml selettivo non viene utilizzato correttamente dall'ottimizzatore? O ho qualcosa che non va? Come ottimizzerei le prestazioni di questa specifica query con l'indicizzazione xml selettiva (o forse la colonna persistente)?

Modifica: Ho eseguito test aggiuntivi con dati di esempio più grandi (~ 274k righe nella tabella con documenti XML vicini alle dimensioni medie di produzione) e confrontato l'indice XML selettivo con una colonna promossa. I risultati provengono dalla traccia di Profiler, che si concentra sull'utilizzo della CPU e sui conteggi di lettura. Il piano di esecuzione per l'indicizzazione xml selettiva è sostanzialmente identico a quanto descritto sopra.

selettivo indice XML e 274k righe (L'esecuzione della query di cui sopra): CPU: 6454, si legge: 938521

Dopo aver aggiornato i valori nel campo cercato di essere unici (record totali ancora 274k) ho avuto la seguenti risultati:

XML selettivo indice e 274k righe (eseguendo la query sopra): CPU: 10077, si legge: 1006466

Quindi utilizzando un promosso (cioè persistente) colonna indicizzata separatamente e utilizzarlo direttamente nella vista : CPU: 0, legge: 23

Le prestazioni dell'indice XML selettivo sembrano essere più vicine alla scansione completa della tabella rispetto al corretto recupero di colonne indicizzate SQL. Ho letto da qualche parte che l'utilizzo dello schema per la tabella potrebbe aiutare a eliminare il passaggio TOP N dal piano di esecuzione (supponendo che stiamo cercando un campo non ripetuto), ma non sono sicuro che sia una possibilità realistica in questo caso.

+0

È possibile caricare l'immagine su un altro sito di hosting di immagini e pubblicare il collegamento qui. Le persone con sufficiente reputazione aiuteranno felicemente a includere l'immagine nel tuo post se ritengono che sia preziosa per la domanda. – har07

+0

Grazie per il suggerimento. Non penso che la foto aggiungerà molto perché ho già digitato i dettagli nel testo. – Consulmagician

+1

Maggiori informazioni su questo in [domanda e risposta su DBA] (http://dba.stackexchange.com/questions/103867/why-is-the-secondary-selective-index-not-used-when-the-where -clause-filters-on) –

risposta

4

L'indice XML selettivo creato viene memorizzato in una tabella interna con la chiave primaria da Orders come colonna principale per la chiave cluster per la tabella interna ei percorsi specificati memorizzati come colonne sparse

Il piano di query si ottiene probabilmente sembra un qualcosa di simile:

enter image description here

Hai una scansione sul tavolo interi ordini con un cercare nella tabella interna sulla chiave primaria per ogni riga Ordini . L'operatore finale del filtro è responsabile della verifica del valore di OrderType restituendo solo le righe corrispondenti.

Non proprio quello che ti aspetteresti da qualcosa chiamato indice.

Al soccorso arriva un indice XML selettivo secondario. Vengono creati per uno dei percorsi specificati nell'indice selettivo primario e creeranno una chiave non cluster sui valori estratti nell'espressione di percorso.

Non è comunque tutto così facile. SQL Server non utilizzerà l'indice secondario sui predicati utilizzati sui valori estratti dalla funzione values(). Devi invece usare exists(). Inoltre, exists() richiede l'utilizzo dei tipi di dati XQUERY nelle espressioni di percorso in cui value() utilizza tipi di dati SQL.

tuo indice XML selettivo primario potrebbe essere la seguente:

CREATE SELECTIVE XML INDEX I_Orders_OrderType ON Orders(xmldoc) 
FOR 
(
    pathOrderType = '/row/c2' as sql nvarchar(20), 
    pathOrderTypeX = '/row/c2/text()' as xquery 'xs:string' maxlength (20) 
) 

Con una secondaria su pathOrderTypeX.

CREATE XML INDEX I_Orders_OrderType2 ON Orders(xmldoc) 
    USING XML INDEX I_Orders_OrderType FOR (pathOrderTypeX) 

E con una query che utilizza exist() otterrete questo piano.

select id 
from V_Orders 
where xmldoc.exist('/row/c2/text()[. = "30791"]') = 1 

enter image description here

La prima cercare è un cercare per il valore che si sta cercando nell'indice non cluster della tabella interna. La ricerca della chiave viene eseguita sulla chiave cluster sulla tabella interna (non so perché sia ​​necessario). E l'ultima ricerca è sulla chiave primaria nella tabella Ordini seguita da un filtro che controlla i valori nulli nella colonna xmldoc.

Se riesci a utilizzare property promotion, creando colonne indicizzate calcolate nella tabella Ordini dall'XML, suppongo che otterrai comunque prestazioni migliori rispetto agli indici XML selettivi secondari.

+0

Risposta eccellente! Grazie. Questo mi ha aiutato a capire come funziona l'indicizzazione selettiva xml sotto il cofano e le restrizioni. Sfortunatamente significa anche che praticamente non posso usarlo nel mio scenario attuale, ma spero che questo sia utile altrove e agli altri. Il motivo per cui l'indicizzazione selettiva non sembra applicarsi al mio problema esatto è che nei casi in cui viene utilizzato .exist, parte della query xpath è nella vista quindi è una combinazione della query effettiva e della query di visualizzazione e questo si traduce nella bel piano d'esecuzione che viene distrutto. – Consulmagician

+0

Nessun problema. Capisco che è necessario esporre la vista ad un utente che non vuole/può occuparsi delle espressioni xpath in 'exists()'. Una volta risolto questo problema costruendo una UDF valutata a livello di tabella che in questo caso prenderebbe come argomento un orderid, usarlo in una query 'exists()' con 'sql: column()' e restituire tutti gli id ​​che corrispondono. Quindi gli utenti della vista devono solo unire l'UDF alla vista e fornire l'orderid come argomento per l'UDF. Oppure l'UDF può essere usato nella clausola where come 'id in (selezionare id da UDF (123456))'. –