Mi pregunta se basa en esto: https://stackoverflow.com/q/35575990/5089204
Para dar una respuesta allí, hice el siguiente escenario de prueba.
Escenario de prueba
Primero creo una tabla de prueba y la lleno con 100.000 filas. Un número aleatorio (0 a 1000) debería conducir a ~ 100 filas para cada número aleatorio. Este número se coloca en una columna varchar y como valor en su XML.
Luego hago una llamada como el OP que necesita con .exist () y con .nodes () con una pequeña ventaja para el segundo, pero ambos toman de 5 a 6 segundos. De hecho, hago las llamadas dos veces: una segunda vez en orden intercambiado y con parámetros de búsqueda ligeramente modificados y con "// item" en lugar de la ruta completa para evitar falsos positivos a través de resultados o planes almacenados en caché.
Luego creo un índice XML y hago las mismas llamadas
Ahora, ¡lo que realmente me sorprendió! - la .nodes
con ruta completa es mucho más lento que antes (9 segundos) pero el .exist()
se ha reducido a la mitad de un segundo, con ruta completa incluso hasta aproximadamente 0,10 seg. (Mientras que .nodes()
con un camino corto es mejor, pero aún muy lejos .exist()
)
Preguntas:
En resumen, mis propias pruebas se abren: los índices XML pueden hacer explotar una base de datos extremadamente. Pueden acelerar las cosas extremadamente (s. Edit 2), pero también pueden ralentizar sus consultas. Me gustaría entender cómo funcionan ... ¿Cuándo se debe crear un índice XML? ¿Por qué puede .nodes()
ser peor con un índice que sin él? ¿Cómo podría uno evitar el impacto 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;
EDITAR 1 - Resultados
Este es un resultado con SQL Server 2012 instalado localmente en una computadora portátil mediana. En esta prueba no pude reproducir el impacto extremadamente negativo en NodesFullPath_with_index
, aunque es más lento que sin el índice ...
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
Prueba EDIT 2 con XML más grande
De acuerdo con la sugerencia de TT, utilicé el XML anterior, pero copié los item
nodos para alcanzar unos 450 elementos. Dejé que el nodo de hit esté muy arriba en el XML (porque creo que eso .exist()
se detendría en el primer hit, mientras .nodes()
continuaría)
La creación del índice XML hizo explotar el archivo mdf a ~ 21 GB, ~ 18 GB parecen pertenecer al índice (!!!)
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()
y.exist()
son convincentes. Además, el hecho de que indexarfull path search
es más rápido parece fácil de entender. Esto significaría: Si crea un índice XML, debe siempre ser consciente de la influencia negativa con cualquier XPath genérico (//
o*
o..
o[filter]
o cualquier cosa no simplemente Xpath ...). De hecho, deberías usar solo la ruta completa, un gran retroceso ...