La mia domanda si basa su questo: https://stackoverflow.com/q/35575990/5089204
Per dare una risposta lì ho fatto il seguente scenario di test.
Scenario di prova
Per prima cosa creo una tabella di test e la riempio con 100.000 righe. Un numero casuale (da 0 a 1000) dovrebbe portare a ~ 100 righe per ogni numero casuale. Questo numero viene inserito in un varchar col e come valore nel tuo XML.
Quindi faccio una chiamata come l'OP che ne ha bisogno con .exist () e con .nodes () con un piccolo vantaggio per il secondo, ma entrambi impiegano dai 5 ai 6 secondi. In effetti eseguo le chiamate due volte: una seconda volta in ordine scambiato e con parametri di ricerca leggermente modificati e con "// item" invece del percorso completo per evitare falsi positivi tramite risultati o piani memorizzati nella cache.
Quindi creo un indice XML e faccio le stesse chiamate
Ora, cosa mi ha davvero sorpreso! - la .nodes
con percorso completo è molto più lento rispetto a prima (9 secondi), ma la .exist()
è giù per mezzo secondo, con percorso completo anche fino a circa 0,10 sec. (mentre .nodes()
con percorso breve è meglio, ma ancora molto indietro .exist()
)
Domande:
I miei test si presentano in breve: gli indici XML possono far esplodere un database estremamente. Possono velocizzare estremamente le cose (vedi modifica 2), ma possono anche rallentare le tue domande. Mi piacerebbe capire come funzionano ... Quando si dovrebbe creare un indice XML? Perché .nodes()
con un indice può essere peggio che senza? Come si può evitare l'impatto negativo?
CREATE TABLE #testTbl(ID INT IDENTITY PRIMARY KEY, SomeData VARCHAR(100),XmlColumn XML);
GO
DECLARE @RndNumber VARCHAR(100)=(SELECT CAST(CAST(RAND()*1000 AS INT) AS VARCHAR(100)));
INSERT INTO #testTbl VALUES('Data_' + @RndNumber,
'<error application="application" host="host" type="exception" message="message" >
<serverVariables>
<item name="name1">
<value string="text" />
</item>
<item name="name2">
<value string="text2" />
</item>
<item name="name3">
<value string="text3" />
</item>
<item name="name4">
<value string="text4" />
</item>
<item name="name5">
<value string="My test ' + @RndNumber + '" />
</item>
<item name="name6">
<value string="text6" />
</item>
<item name="name7">
<value string="text7" />
</item>
</serverVariables>
</error>');
GO 100000
DECLARE @d DATETIME=GETDATE()
SELECT #testTbl.*
FROM #testTbl
CROSS APPLY XmlColumn.nodes('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') AS a(b);
SELECT CAST(GETDATE()-@d AS TIME) AS NodesFullPath_no_index;
GO
DECLARE @d DATETIME=GETDATE();
SELECT *
FROM #testTbl
WHERE XmlColumn.exist('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') = 1;
SELECT CAST(GETDATE()-@d AS TIME) AS ExistFullPath_no_index;
GO
DECLARE @d DATETIME=GETDATE();
SELECT *
FROM #testTbl
WHERE XmlColumn.exist('//item[@name="name5" and value/@string="My test 500"]') = 1;
SELECT CAST(GETDATE()-@d AS TIME) AS ExistShortPath_no_index;
GO
DECLARE @d DATETIME=GETDATE()
SELECT #testTbl.*
FROM #testTbl
CROSS APPLY XmlColumn.nodes('//item[@name="name5" and value/@string="My test 500"]') AS a(b);
SELECT CAST(GETDATE()-@d AS TIME) AS NodesShortPath_no_index;
GO
CREATE PRIMARY XML INDEX PXML_test_XmlColum1 ON #testTbl(XmlColumn);
CREATE XML INDEX IXML_test_XmlColumn2 ON #testTbl(XmlColumn) USING XML INDEX PXML_test_XmlColum1 FOR PATH;
GO
DECLARE @d DATETIME=GETDATE()
SELECT #testTbl.*
FROM #testTbl
CROSS APPLY XmlColumn.nodes('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') AS a(b);
SELECT CAST(GETDATE()-@d AS TIME) AS NodesFullPath_with_index;
GO
DECLARE @d DATETIME=GETDATE();
SELECT *
FROM #testTbl
WHERE XmlColumn.exist('/error/serverVariables/item[@name="name5" and value/@string="My test 600"]') = 1;
SELECT CAST(GETDATE()-@d AS TIME) AS ExistFullPath_with_index;
GO
DECLARE @d DATETIME=GETDATE();
SELECT *
FROM #testTbl
WHERE XmlColumn.exist('//item[@name="name5" and value/@string="My test 500"]') = 1;
SELECT CAST(GETDATE()-@d AS TIME) AS ExistShortPath_with_index;
GO
DECLARE @d DATETIME=GETDATE()
SELECT #testTbl.*
FROM #testTbl
CROSS APPLY XmlColumn.nodes('//item[@name="name5" and value/@string="My test 500"]') AS a(b);
SELECT CAST(GETDATE()-@d AS TIME) AS NodesShortPath_with_index;
GO
DROP TABLE #testTbl;
EDIT 1 - Risultati
Questo è un risultato con SQL Server 2012 installato localmente su un laptop medio In questo test non sono riuscito a riprodurre l'impatto estremamente negativo su NodesFullPath_with_index
, sebbene sia più lento che senza l'indice ...
NodesFullPath_no_index 6.067
ExistFullPath_no_index 6.223
ExistShortPath_no_index 8.373
NodesShortPath_no_index 6.733
NodesFullPath_with_index 7.247
ExistFullPath_with_index 0.217
ExistShortPath_with_index 0.500
NodesShortPath_with_index 2.410
EDIT 2 Test con XML più grande
Secondo il suggerimento di TT, ho usato l'XML sopra, ma item
ho copiato i nodi - nodo per raggiungere circa 450 elementi. Ho lasciato che l'hit-node fosse molto in alto nell'XML (perché penso che .exist()
si fermerebbe al primo hit, mentre .nodes()
continuerebbe)
La creazione dell'indice XML ha fatto saltare il file mdf a ~ 21 GB, ~ 18 GB sembrano appartenere all'indice (!!!)
NodesFullPath_no_index 3min44
ExistFullPath_no_index 3min39
ExistShortPath_no_index 3min49
NodesShortPath_no_index 4min00
NodesFullPath_with_index 8min20
ExistFullPath_with_index 8,5 seconds !!!
ExistShortPath_with_index 1min21
NodesShortPath_with_index 13min41 !!!
.nodes()
e.exist()
sono convincenti. Anche il fatto che l'indice confull path search
sia più veloce sembra facile da capire. Ciò significherebbe: se crei un indice XML, devi sempre essere consapevole dell'influenza negativa con qualsiasi XPath generico (//
o*
o..
o[filter]
o qualsiasi altra cosa non solo Xpath ...). In effetti, dovresti usare solo il percorso completo - un grande pareggio ...