Tengo una tabla bastante grande con una de las columnas que son datos XML con un tamaño promedio de entrada XML de ~ 15 kilobytes. Todas las demás columnas son entradas regulares, bigints, GUID, etc. Para tener algunos números concretos, digamos que la tabla tiene un millón de filas y tiene un tamaño de ~ 15 GB.
Lo que noté es que esta tabla es realmente lenta para seleccionar datos si quiero seleccionar todas las columnas. Cuando lo hago
SELECT TOP 1000 * FROM TABLE
toma alrededor de 20-25 segundos leer los datos del disco, aunque no impongo ningún orden en el resultado. Ejecuto la consulta con el caché frío (es decir, después DBCC DROPCLEANBUFFERS
). Aquí están los resultados de las estadísticas de IO:
Cuenta de escaneo 1, lecturas lógicas 364, lecturas físicas 24, lecturas de lectura anticipada 7191, lecturas lógicas lob 7924, lecturas físicas lob 1690, lecturas lob de lectura anticipada 3968.
Toma ~ 15 MB de datos. El plan de ejecución muestra la exploración de índice agrupado como era de esperar.
No hay IO en el disco además de mis consultas; También he comprobado que la fragmentación del índice agrupado es cercana al 0%. Esta es una unidad SATA de nivel de consumidor, sin embargo, todavía creo que SQL Server podría escanear la tabla más rápido que ~ 100-150 MB / min.
La presencia del campo XML hace que la mayoría de los datos de la tabla se ubiquen en las páginas LOB_DATA (de hecho, ~ 90% de las páginas de la tabla son LOB_DATA).
Creo que mi pregunta es: ¿estoy en lo cierto al pensar que las páginas LOB_DATA pueden causar escaneos lentos no solo por su tamaño, sino también porque SQL Server no puede escanear el índice agrupado de manera efectiva cuando hay muchas páginas LOB_DATA en la tabla?
Aún más ampliamente: ¿se considera razonable tener una estructura de tabla / patrón de datos de este tipo? Las recomendaciones para usar Filestream generalmente indican tamaños de campo mucho más grandes, por lo que realmente no quiero ir por esa ruta. Realmente no he encontrado ninguna buena información sobre este escenario en particular.
He estado pensando en la compresión XML, pero debe hacerse en el cliente o con SQLCLR y requeriría bastante trabajo para implementarse en el sistema.
Probé la compresión, y dado que los XML son muy redundantes, puedo (en la aplicación ac #) comprimir XML de 20KB a ~ 2.5KB y almacenarlo en una columna VARBINARIA, evitando el uso de páginas de datos LOB. Esto acelera SELECTs 20 veces en mis pruebas.
fuente
SELECT *
no es el problema si necesitas los datos XML. Solo es un problema si no desea los datos XML, en cuyo caso, ¿por qué ralentizar la consulta para recuperar datos que no utiliza? Pregunté sobre las actualizaciones del XML preguntándome si la fragmentación en las páginas LOB no se informaba con precisión. ¿Por eso pregunté en mi respuesta cómo determinó exactamente que el índice agrupado no estaba fragmentado? ¿Puede proporcionar el comando que ejecutó? ¿Y ha realizado una RECONSTRUCCIÓN completa en el Índice agrupado? (continuación)Respuestas:
Simplemente tener la columna XML en la tabla no tiene ese efecto. Es la presencia de datos XML lo que, bajo ciertas condiciones , hace que una parte de los datos de una fila se almacene fuera de la fila, en páginas LOB_DATA. Y aunque uno (o tal vez varios ;-) podría argumentar que duh, la
XML
columna implica que efectivamente habrá datos XML, no está garantizado que los datos XML necesiten almacenarse fuera de la fila: a menos que la fila ya esté casi llena Además de ser datos XML, los documentos pequeños (hasta 8000 bytes) pueden encajar en fila y nunca ir a una página LOB_DATA.El escaneo se refiere a mirar todas las filas. Por supuesto, cuando se lee una página de datos, se leen todos los datos de la fila , incluso si seleccionó un subconjunto de las columnas. La diferencia con los datos LOB es que si no selecciona esa columna, los datos fuera de la fila no se leerán. Por lo tanto, no es realmente justo llegar a una conclusión sobre qué tan eficientemente SQL Server puede escanear este índice agrupado ya que no lo probó exactamente (o probó la mitad). Seleccionó todas las columnas, que incluye la columna XML, y como mencionó, allí es donde se encuentra la mayoría de los datos.
Entonces, ya sabemos que la
SELECT TOP 1000 *
prueba no fue simplemente leer una serie de páginas de datos de 8k, todo en una fila, sino saltar a otras ubicaciones por cada fila . La estructura exacta de esos datos LOB puede variar en función de su tamaño. Según la investigación que se muestra aquí ( ¿Cuál es el tamaño del puntero LOB para tipos (MAX) como Varchar, Varbinary, Etc? ), Existen dos tipos de asignaciones LOB fuera de fila:Una de estas dos situaciones ocurre cada vez que recupera datos LOB que tienen más de 8000 bytes o que simplemente no encajan en la fila. Publiqué una secuencia de comandos de prueba en PasteBin.com (secuencia de comandos T-SQL para probar las asignaciones y lecturas de LOB ) que muestra los 3 tipos de asignaciones de LOB (en función del tamaño de los datos), así como el efecto que cada uno de ellos tiene en lógica y lecturas físicas En su caso, si los datos XML realmente son menos de 42,000 bytes por fila, entonces ninguno de ellos (o muy poco) debería estar en la estructura TEXT_TREE menos eficiente.
Si desea probar qué tan rápido SQL Server puede escanear ese índice agrupado, haga lo siguiente
SELECT TOP 1000
pero especifique una o más columnas sin incluir esa columna XML. ¿Cómo afecta eso a sus resultados? Debería ser bastante más rápido.Dado que tenemos una descripción incompleta de la estructura real de la tabla y el patrón de datos, cualquier respuesta puede no ser óptima dependiendo de cuáles son esos detalles faltantes. Con eso en mente, diría que no hay nada obviamente irrazonable sobre la estructura de su tabla o patrón de datos.
Eso hizo que la selección de todas las columnas, o incluso solo los datos XML (ahora en
VARBINARY
), sea más rápido, pero en realidad perjudica las consultas que no seleccionan los datos "XML". Suponiendo que tiene aproximadamente 50 bytes en las otras columnas y tiene unFILLFACTOR
de 100, entonces:Sin compresión: 15k de
XML
datos deben requerir 2 páginas LOB_DATA, que luego requieren 2 punteros para la raíz en línea. El primer puntero tiene 24 bytes y el segundo 12, para un total de 36 bytes almacenados en fila para los datos XML. El tamaño total de la fila es de 86 bytes, y puede caber aproximadamente 93 de esas filas en una página de datos de 8060 bytes. Por lo tanto, 1 millón de filas requiere 10,753 páginas de datos.Compresión personalizada: 2.5k de
VARBINARY
datos encajarán en fila. El tamaño total de la fila es 2610 (2.5 * 1024 = 2560) bytes, y solo puede ajustar 3 de esas filas en una página de datos de 8060 bytes. Por lo tanto, 1 millón de filas requiere 333,334 páginas de datos.Ergo, la implementación de resultados de compresión personalizados en un aumento de 30 veces en las páginas de datos para el índice agrupado. Es decir, todas las consultas que usan un escaneo de índice agrupado ahora tienen aproximadamente 322,500 más páginas de datos para leer. Consulte la sección detallada a continuación para conocer las ramificaciones adicionales de hacer este tipo de compresión.
Yo advertiría contra cualquier refactorización basada en el rendimiento de
SELECT TOP 1000 *
. No es probable que se trate de una consulta que la aplicación incluso emitirá, y no debe usarse como la única base para optimizaciones potencialmente innecesarias.Para obtener información más detallada y más pruebas para probar, consulte la sección a continuación.
No se puede dar una respuesta definitiva a esta pregunta, pero al menos podemos avanzar y sugerir investigaciones adicionales para ayudarnos a acercarnos a resolver el problema exacto (idealmente basado en evidencia).
Lo que sabemos:
XML
columna y varios otros tipos de columnas:INT
,BIGINT
,UNIQUEIDENTIFIER
, "etc."XML
el "tamaño" de la columna es, en promedio, aproximadamente 15kDBCC DROPCLEANBUFFERS
, la siguiente consulta tarda entre 20 y 25 segundos en completarse:SELECT TOP 1000 * FROM TABLE
Lo que creemos que sabemos:
La compresión XML podría ayudar. ¿Cómo exactamente harías la compresión en .NET? ¿A través de las clases GZipStream o DeflateStream ? Esta no es una opción de costo cero. Sin duda, comprimirá algunos de los datos en un gran porcentaje, pero también requerirá más CPU, ya que necesitará un proceso adicional para comprimir / descomprimir los datos cada vez. Este plan también eliminaría por completo su capacidad para:
.nodes
,.value
,.query
, y.modify
funciones XML.indexar los datos XML.
Tenga en cuenta (ya que mencionó que XML es "altamente redundante") que el
XML
tipo de datos ya está optimizado, ya que almacena los nombres de elementos y atributos en un diccionario, asignando una ID de índice entero a cada elemento y luego usando esa ID entera en todo el documento (por lo tanto, no repite el nombre completo por cada uso, ni lo repite nuevamente como una etiqueta de cierre para los elementos). Los datos reales también tienen espacios en blanco extraños eliminados. Es por eso que los documentos XML extraídos no conservan su estructura original y por qué los elementos vacíos se extraen como<element />
si fueran como<element></element>
. Por lo tanto, cualquier ganancia de la compresión a través de GZip (o cualquier otra cosa) solo se encontrará comprimiendo los valores del elemento y / o atributo, que es un área de superficie mucho más pequeña que podría mejorarse de lo que la mayoría esperaría, y lo más probable es que no valga la pena perder capacidades como se señaló directamente arriba.También tenga en cuenta que comprimir los datos XML y almacenar el
VARBINARY(MAX)
resultado no eliminará el acceso LOB, solo lo reducirá. Dependiendo del tamaño del resto de los datos en la fila, el valor comprimido podría encajar en la fila o aún podría requerir páginas LOB.Esa información, si bien es útil, no es suficiente. Hay muchos factores que influyen en el rendimiento de las consultas, por lo que necesitamos una imagen mucho más detallada de lo que está sucediendo.
Lo que no sabemos, pero necesitamos:
SELECT *
materia? ¿Es este un patrón que usas en el código? Si es así, ¿por qué?SELECT TOP 1000 XmlColumn FROM TABLE;
:?La cantidad de 20 a 25 segundos que lleva devolver estas 1000 filas está relacionada con factores de red (obtener los datos a través del cable), y cuánto está relacionado con factores del cliente (lo que representa aproximadamente 15 MB más el resto de ¿Datos XML en la cuadrícula en SSMS, o posiblemente guardarlos en el disco)?
A veces, se pueden descomponer estos dos aspectos de la operación simplemente no devolviendo los datos. Ahora, uno podría pensar seleccionar en una tabla temporal o variable de tabla, pero esto solo introduciría algunas nuevas variables (es decir, E / S de disco para
tempdb
, escritura del registro de transacciones, posible crecimiento automático de datos tempdb y / o archivo de registro). espacio en la agrupación de almacenamiento intermedio, etc.). Todos esos factores nuevos pueden aumentar el tiempo de consulta. En cambio, normalmente almaceno las columnas en variables (del tipo de datos apropiado; noSQL_VARIANT
) que se sobrescriben con cada nueva fila (es decirSELECT @Column1 = tab.Column1,...
).SIN EMBARGO , como lo señaló @PaulWhite en este DBA. Preguntas y respuestas de StackExchange, las lecturas lógicas son diferentes al acceder a los mismos datos de LOB , con mi propia investigación adicional publicada en PasteBin ( secuencia de comandos T-SQL para probar varios escenarios para lecturas de LOB ) , LOB no se accede consistentemente entre
SELECT
,SELECT INTO
,SELECT @XmlVariable = XmlColumn
,SELECT @XmlVariable = XmlColumn.query(N'/')
, ySELECT @NVarCharVariable = CONVERT(NVARCHAR(MAX), XmlColumn)
. Entonces, nuestras opciones son un poco más limitadas aquí, pero esto es lo que se puede hacer:Como alternativa, puede ejecutar la consulta a través de sqlcmd.exe y dirigir la salida para ir a ninguna parte a través de:
-o NUL:
.¿Cuál es el tamaño de datos real para las
XML
columnas que se devuelven ? El tamaño promedio de esa columna en toda la tabla realmente no importa si las filas "TOP 1000" contienen una porción desproporcionadamente grande de losXML
datos totales . Si desea saber acerca de las 1000 filas principales, mire esas filas. Por favor ejecute lo siguiente:CREATE TABLE
, incluidos todos los índices.¿Cuáles son los resultados exactos de la siguiente consulta?
ACTUALIZAR
Se me ocurrió que debería intentar reproducir este escenario para ver si experimento un comportamiento similar. Entonces, creé una tabla con varias columnas (similar a la descripción vaga en la Pregunta), y luego la llené con 1 millón de filas, y la columna XML tiene aproximadamente 15k de datos por fila (vea el código a continuación).
Lo que descubrí es que se
SELECT TOP 1000 * FROM TABLE
completa en 8 segundos la primera vez, y de 2 a 4 segundos cada vez (sí, se ejecutaDBCC DROPCLEANBUFFERS
antes de cada ejecución de laSELECT *
consulta). Y mi computadora portátil de varios años no es rápida: SQL Server 2012 SP2 Developer Edition, 64 bits, 6 GB de RAM, doble 2.5 Ghz Core i5 y una unidad SATA de 5400 RPM. También estoy ejecutando SSMS 2014, SQL Server Express 2014, Chrome y varias otras cosas.En función del tiempo de respuesta de mi sistema, repetiré que necesitamos más información (es decir, detalles sobre la tabla y los datos, los resultados de las pruebas sugeridas, etc.) para ayudar a reducir la causa del tiempo de respuesta de 20 a 25 segundos que estas viendo
Y, debido a que queremos factorizar el tiempo necesario para leer las páginas que no son LOB, ejecuté la siguiente consulta para seleccionar todas menos la columna XML (una de las pruebas que sugerí anteriormente). Esto regresa en 1,5 segundos de manera bastante consistente.
Conclusión (por el momento) En
base a mi intento de recrear su escenario, no creo que podamos señalar ni la unidad SATA ni la E / S no secuencial como la causa principal de los 20-25 segundos, especialmente porque todavía No sé qué tan rápido vuelve la consulta cuando no se incluye la columna XML. Y no pude reproducir la gran cantidad de lecturas lógicas (no LOB) que está mostrando, pero tengo la sensación de que necesito agregar más datos a cada fila a la luz de eso y la declaración de:
Mi tabla tiene 1 millón de filas, cada una con poco más de 15k de datos XML, y
sys.dm_db_index_physical_stats
muestra que hay 2 millones de páginas LOB_DATA. El 10% restante sería 222k páginas de datos IN_ROW, sin embargo, solo tengo 11,630 de ellas. Entonces, una vez más, necesitamos más información sobre el esquema real de la tabla y los datos reales.fuente
Sí, leer datos LOB no almacenados en fila conduce a E / S aleatorias en lugar de E / S secuenciales. La métrica de rendimiento del disco para usar aquí para comprender por qué es rápida o lenta es Random Read IOPS.
Los datos LOB se almacenan en una estructura de árbol donde la página de datos en el índice agrupado apunta a una página de datos LOB con una estructura raíz LOB que a su vez apunta a los datos LOB reales. Al atravesar los nodos raíz en el índice agrupado, SQL Server solo puede obtener los datos en fila mediante lecturas secuenciales. Para obtener los datos LOB, SQL Server tiene que ir a otro lugar en el disco.
Supongo que si cambiaste a un disco SSD no sufrirías tanto de esto, ya que los IOPS aleatorios para un SSD son mucho más altos que para un disco giratorio.
Si podria ser. Depende de lo que esta mesa esté haciendo por ti.
Por lo general, los problemas de rendimiento con XML en SQL Server ocurren cuando desea usar T-SQL para consultar el XML y aún más cuando desea usar valores del XML en un predicado en una cláusula where o join. Si ese es el caso, podría echar un vistazo a la promoción de la propiedad o los índices selectivos de XML o un rediseño de las estructuras de su tabla triturando el XML en las tablas.
Lo hice una vez en un producto hace un poco más de 10 años y desde entonces me he arrepentido. Realmente extrañé no poder trabajar con los datos usando T-SQL, por lo que no se lo recomendaría a nadie si se puede evitar.
fuente