Rendimiento

Parametrización Forzada en SQL Server

Hoy te voy a hablar de una opción muy interesante que puede mejorar el rendimiento de tus consultas: la parametrización forzada. ¿Qué es esto y cómo funciona? Te lo explico en este artículo.

Como ya sabes, si has leído mis artículos anteriores sobre planes de ejecución, caché de planes y optimización para consultas ad hoc, SQL Server genera un plan de ejecución óptimo para cada consulta que recibe, basándose en las estadísticas, los índices y otros factores. Este plan se almacena en la caché de planes para reutilizarlo si se vuelve a ejecutar la misma consulta. Sin embargo, hay casos en los que la misma consulta puede tener diferentes valores de parámetros, lo que hace que el plan óptimo varíe. Por ejemplo, si tenemos una consulta que busca los clientes de una ciudad determinada, el plan óptimo puede cambiar según la ciudad que se especifique.

¿Qué es la parametrización?

En estos casos, SQL Server tiene dos opciones: generar un nuevo plan para cada valor de parámetro diferente (lo que se llama parametrización simple) o generar un plan genérico que sirva para todos los valores posibles (lo que se llama parametrización forzada). La primera opción tiene la ventaja de que el plan se adapta al valor concreto, pero tiene el inconveniente de que consume más recursos y puede llenar la caché de planes con planes similares. La segunda opción tiene la ventaja de que ahorra recursos y evita la fragmentación de la caché de planes, pero tiene el inconveniente de que el plan puede no ser el más adecuado para algunos valores.

¿Qué es la parametrización forzada?

La parametrización forzada hace que SQL Server trate todas las consultas como si fueran procedimientos almacenados con parámetros, y genera un plan genérico para cada consulta, independientemente del valor de los parámetros. Esto puede mejorar el rendimiento en algunos casos, pero también puede empeorarlo en otros. Por eso hay que tener cuidado al usar esta opción y hacer pruebas antes de aplicarla en un entorno productivo.

¿Es mejor la parametrización forzada?

Entonces, ¿cuál es la mejor opción? Depende. No hay una respuesta única, sino que depende del tipo de consultas, de la distribución de los datos, del tamaño de la base de datos y de otros factores. Lo ideal sería poder elegir la opción más conveniente para cada consulta, pero eso no es posible con la parametrización simple. Por eso existe la parametrización forzada, que nos permite activar esta opción a nivel de base de datos o a nivel de consulta individual, mediante una directiva o una sugerencia (HINT).

¿Cuándo activar la parametrización forzada?

Hemos visto que la parametrización forzada puede ayudarnos a reducir recursos al reutilizar los planes de ejecución de las consultas aunque cambien los parámetros. Imagina que tenemos una base de datos que da servicio a una aplicación web con miles o millones de usuarios lanzando la misma consulta, cada uno para sus datos. Esto nos va a generar miles de millones de planes de ejecución, en su mayoría iguales que van a consumir recursos para generarse teniendo ya otro igual guardado y que van a llenar la caché.

Sin embargo esto puede perjudicar mucho el rendimiento si la distribución de nuestros datos no es uniforme. Pero no todo está perdido, a partir de SQL 2019, con los joins dinámicos este problema se reduce en gran medida. 

¿Cómo activo la parametrización forzada?

Si llegado a este punto crees que esta solución es para ti, has detectado muchas recompilaciones que están llenando tu caché, has visto que tus consultas invierten mucho tiempo en compilar planes o simplemente quieres hacer pruebas vamos a ver como activarlo. Como hemos dicho además de con un HINT en cada consulta podemos activar la parametrización forzada por base de datos. Podremos hacerlo desde SSMS, en la pestaña opciones de las propiedades de nuestra base de datos o con el siguiente script:

Una vez habilitado, debería bajar el consumo de CPU de nuestro SQL, si, por el contrario, no es así deberás volver atrás y pensar en otras soluciones como la optimización para consultas Ad Hoc o analizar más a fondo con Query Store tus planes de ejecución.

Conclusión

En este artículo te he explicado qué es la parametrización forzada y cómo funciona. También te he mostrado las ventajas y los inconvenientes de esta opción y cómo puedes activarla o desactivarla según tus necesidades. Espero que te haya resultado útil y que lo pongas en práctica. Recuerda que puedes dejarme tus comentarios o preguntas al final del artículo, en Twitter o por mail. Hasta la próxima.

Publicado por Roberto Carrancio en Rendimiento, SQL Server, 2 comentarios

Optimizando SQL Server para consultas Ad Hoc

En este artículo vamos a hablar de una opción de SQL Server que puede mejorar el rendimiento de las consultas ad hoc, es decir, aquellas que se generan dinámicamente y no se repiten con frecuencia. Se trata de la opción OPTIMIZE FOR AD HOC WORKLOADS, que modifica el comportamiento del caché de planes de ejecución. El objetivo de esto es reducir el consumo de recursos de nuestro servidor como vamos a ver a continuación.

Antes de optimizar para Ad Hoc

Para poder saber si esta es una solución ideal para nosotros tenemos que tener claro una serie de conceptos previos. Estos son los planes de ejecución y la caché de planes. Nosotros los hemos tratado ya en los anteriores artículos en el blog pero aún así vamos a revisarlos.

Como sabemos, los planes de ejecución son la forma en la que el motor de base de datos ejecuta las consultas. Son una guía paso a paso dividida en componentes atómicos que dicen todo lo que va a necesitar hacer SQL Server para devolvernos los datos solicitados.

Como también sabemos, la caché de planes de ejecución es una estructura de memoria que almacena los planes generados por el optimizador para las consultas que se ejecutan en el servidor. Esto permite reutilizar los planes y ahorrar el coste de generarlos cada vez que se ejecuta una consulta. Sin embargo, la caché de planes tiene un tamaño limitado y puede llenarse rápidamente si se ejecutan muchas consultas diferentes. En ese caso, el servidor tiene que eliminar los planes más antiguos o menos usados para hacer espacio a los nuevos, lo que se conoce como recompilaciones. Las recompilaciones pueden afectar al rendimiento del servidor y consumir recursos de CPU y memoria.

Activar la optimización para consultas Ad Hoc

La opción OPTIMIZE FOR AD HOC WORKLOADS ayuda a reducir el impacto de las consultas ad hoc en la caché de planes. Lo que hace es almacenar solamente una parte del plan (un stub) la primera vez que se ejecuta una consulta ad hoc, en lugar del plan completo. Esto ocupa menos espacio en la caché y permite almacenar más planes. Si la consulta se vuelve a ejecutar, entonces se almacena el plan completo y se puede reutilizar. De esta forma, se evita llenar la caché con planes que solo se usan una vez y se favorece la reutilización de los planes que se repiten.

Para activar esta opción, se puede usar el siguiente comando a nivel de base de datos:

Esta opción solo afecta a la base de datos actual y se puede activar o desactivar en cualquier momento. Sin embargo, también existe la posibilidad de configurar esta opción a nivel de instancia, lo que afecta a todas las bases de datos del servidor. Para ello, se puede usar el siguiente comando:

Al activar esta opción a nivel de instancia, podemos aplicar los beneficios de la optimización para consultas ad hoc a todo el servidor, sin tener que hacerlo individualmente para cada base de datos. Esto puede ser útil cuando tenemos un entorno con muchas bases de datos y muchas consultas dinámicas.

¿Merece la pena activar la optimización para Ad Hoc?

Ahora bien, ¿cuándo debemos activar esta opción y cuándo no? La respuesta depende del tipo de carga de trabajo que tengamos en nuestro servidor. Si tenemos un entorno con pocas consultas ad hoc y muchas consultas repetitivas, entonces no es necesario activar esta opción, ya que no tendremos problemas con el caché de planes. Por el contrario, si tenemos un entorno con muchas consultas ad hoc y pocas consultas repetitivas, entonces sí es conveniente activar esta opción, ya que podremos ahorrar espacio en el caché y evitar recompilaciones innecesarias.

Pero ¿cómo saber el número de consultas ad hoc que tenemos en nuestro servidor? Una forma sencilla es usar la vista dinámica de gestión sys.dm_exec_cached_plans, que nos muestra los planes almacenados en el caché. Podemos filtrar por el tipo de objeto ‘Adhoc’ y contar cuánta cache ocupan estos planes. Por ejemplo:

Este comando nos devuelve el espacio consumido por planes ad hoc en la caché. Podemos compararlo con el espacio total de los planes en caché para ver qué porcentaje representan. Si es mayor al 25%, entonces podemos considerar activar la opción OPTIMIZE FOR AD HOC WORKLOADS. Tened en cuenta que los datos de la vista sys.dm_exec_cached_plans se reinician con cada reinicio del servicio de SQL Server por lo que antes de ejecutarlo debemos asegurarnos que el servidor lleva encendido bastante tiempo para tener datos fiables.

Conclusión

En este artículo hemos visto cómo la opción OPTIMIZE FOR AD HOC WORKLOADS puede mejorar el rendimiento de las consultas ad hoc en SQL Server. Como hemos comentado en la introducción también vamos a lograr reducir el consumo de recursos. Esta opción es especialmente útil cuando tenemos un entorno con muchas consultas dinámicas que no se repiten con frecuencia y que pueden llenar el caché de planes rápidamente. Al activar esta opción, podemos reducir el espacio ocupado por los planes en el caché (consumo de memoria RAM), evitar las recompilaciones innecesarias (Consumo de CPU) y favorecer la reutilización de los planes que se ejecutan más de una vez (Consumo de CPU).

Además, hemos visto cómo podemos configurar esta opción a nivel de base de datos o a nivel de instancia, según nuestras necesidades. También hemos visto cómo podemos saber el número de consultas ad hoc que tenemos en nuestro servidor y cuándo debemos activar o no esta opción. Esperamos que este artículo te haya sido útil y te invitamos a leer nuestros artículos anteriores sobre planes de ejecución y caché de planes.

Publicado por Roberto Carrancio en Rendimiento, SQL Server, 0 comentarios

Caché de planes de ejecución en SQL Server

En el artículo anterior, vimos cómo funcionan los planes de ejecución en SQL Server y cómo podemos analizarlos para optimizar nuestras consultas. Hoy vamos a profundizar en un aspecto muy importante de los planes de ejecución: la caché de planes.

La caché de planes es el espacio de memoria donde SQL Server almacena los planes de ejecución que ha generado previamente, para poder reutilizarlos si se vuelve a ejecutar la misma consulta. Esto tiene varias ventajas, como evitar el coste de generar un nuevo plan cada vez, y facilitar el uso de parámetros en las consultas.

¿Cómo ver los planes en caché?

Para ver los planes de ejecución almacenados en caché usaremos la vista de sistema sys.dm_exec_cached_plans y nos ayudaremos de las funciones sys.dm_exec_sql_text y sys.dm_exec_query_plan para ver el texto de la consulta y el plan de ejecución en formato XML. Está será la consulta que usaremos:

¿Cómo se almacenan los planes en caché?

Los más avispados os habréis dado cuenta que el plan de ejecución se almacena usando el campo plan_handle, este corresponde a un hash de la consulta que estamos ejecutando. Esto quiere decir que cualquier mínima variación en el texto de la consulta generará un nuevo plan que se almacenará en la caché, aunque la consulta sea la misma y solo hayamos añadido espacios o tabuladores. Esto puede ser un problema y tenemos que tenerlo en cuenta ya que puede ser que almacenemos multitud de planes en caché para una misma consulta.

¿Qué pasa si la caché se llena y no hay espacio para nuevos planes?

Si la caché se llena, SQL Server empieza a eliminar los planes más antiguos o menos usados, siguiendo un algoritmo llamado LRU (least recently used). Esto puede provocar que se pierdan planes óptimos y que se tengan que generar nuevos planes cada vez, lo que afecta al rendimiento. Para evitarlo, podemos aumentar el tamaño de la caché, usando la opción ‘max server memory’, o podemos limpiar la caché manualmente, usando el comandos DBCC. Tenemos que evitar vaciar completamente la caché en nuestros entornos de producción pues todos las nuevas consultas tendrán que compilar de nuevo sus planes de ejecución consumiendo más recursos. También tendremos que tener en cuenta que al ser una caché en memoria no es persistente, se borrará completamente con cada reinicio del servicio.

¿Puede ser que los planes de la caché no sean los más adecuados para las consultas actuales?

Si los datos han sufrido una variación tal que los planes en caché no son los más adecuados para las consultas actuales, podemos tener un problema de recompilación o de parámetro erróneo. La recompilación ocurre cuando SQL Server detecta que el plan existente no es válido o no es óptimo para la consulta actual, y genera uno nuevo. Esto puede ser beneficioso en algunos casos, pero también puede consumir recursos y tiempo. El parámetro erróneo ocurre cuando SQL Server usa el mismo plan para consultas con diferentes valores de parámetros, lo que puede provocar que el plan no sea eficiente para algunos valores. Para evitar estos problemas, podemos usar la opción ‘optimize for’ o ‘recompile’ en las consultas, o podemos usar procedimientos almacenados con parámetros.

¿Qué pasa si hay demasiados planes similares que ocupan espacio innecesariamente?

Si hay demasiados planes similares que ocupan espacio innecesariamente, podemos tener un problema de crecimiento de la caché. Esto ocurre cuando SQL Server genera planes diferentes para consultas que solo cambian en los valores literales, lo que hace que la caché se llene de planes redundantes. Para evitarlo, podemos usar parámetros en las consultas, o podemos usar la opción ‘forced parameterization’ en las bases de datos.

Uso de procedimientos almacenados para optimizar la caché

Una forma de reducir el impacto de estos problemas es usar procedimientos almacenados en lugar de consultas ad hoc. Los procedimientos almacenados son bloques de código SQL que se almacenan en la base de datos y se pueden ejecutar con diferentes valores de parámetros. 

Los procedimientos almacenados tienen varias ventajas respecto a las consultas ad hoc:
– Solo generan un plan de ejecución por cada procedimiento almacenado, independientemente del valor de los parámetros. Esto evita la inflación de la caché y el parámetro erróneo.
– Solo recompilan el plan si hay cambios en el código del procedimiento almacenado o en la estructura de las tablas. Esto evita la recompilación innecesaria y mejora el rendimiento.
– Permiten encapsular la lógica de negocio y facilitar el mantenimiento y la seguridad del código.

Sin embargo, los procedimientos almacenados también tienen algunos inconvenientes:
– Pueden ser más difíciles de escribir y depurar que las consultas ad hoc.
– Pueden tener problemas de rendimiento si el plan generado no es óptimo para todos los valores posibles de los parámetros. Desde SQL 2019 esto se soluciona en gran medida gracias al uso del componente adaptive join al persistir los planes en caché. En versiones anteriores de SQL tendríamos que recurrir a usar la opción ‘recompile’ o ‘optimize for’ en nuestro procedimiento.
– Pueden tener problemas de seguridad si no se usan correctamente las opciones ‘execute as’ o ‘with encryption’.

Conclusión

La caché de planes es un elemento clave para el rendimiento de SQL Server, ya que permite reutilizar los planes de ejecución y ahorrar recursos. Pero también hay que tener cuidado con los posibles problemas que puede causar, como la pérdida de memoria, los planes ineficientes o la inconsistencia de datos. Para evitarlos, podemos usar herramientas y opciones de configuración que nos permitan controlar y optimizar la caché de planes. Además, podemos usar procedimientos almacenados en lugar de consultas ad hoc, para reducir el número de planes generados y mejorar la calidad del código.

Si quieres convertirte en un experto en planes de ejecución y mejorar el rendimiento de tus bases de datos, no te pierdas los siguientes artículos. Y si tienes alguna duda o comentario, no dudes en escribirme aquí abajo, en Twitter o por mail. Estaré encantado de ayudarte.

Publicado por Roberto Carrancio en Rendimiento, SQL Server, 2 comentarios

Planes de Ejecución en SQL Server

Hoy vamos a hablar de los planes de ejecución en SQL Server, uno de los temas más amplios y a la vez imprescindibles para entender el rendimiento de nuestras consultas. Entender los planes de ejecución es básico para analizar el rendimiento de las consultas y optimizarlas. Empecemos por el principio.

¿Qué son los planes de ejecución? 

Los planes de ejecución son una representación gráfica o textual de cómo SQL Server procesa una consulta. Cada consulta se divide en una serie de operadores lógicos y físicos que realizan diferentes tareas, como leer datos de una tabla, filtrar filas, ordenar resultados, etc. Además, los planes de ejecución nos muestran el orden y el coste de cada operador, así como las estadísticas de las filas procesadas y el uso de memoria y CPU. 

¿Para qué sirven los planes de ejecución? 

Los planes de ejecución nos ayudan a entender cómo SQL Server ejecuta una consulta y a identificar posibles problemas de rendimiento. Por ejemplo, podemos ver si una consulta usa índices adecuados, si hay operadores costosos o ineficientes, si hay estimaciones erróneas de cardinalidad, etc. Con esta información, podemos aplicar técnicas de optimización, como crear o modificar índices, reescribir la consulta, actualizar las estadísticas, etc. 

¿Cómo se obtienen los planes de ejecución? 

Hay varias formas de obtener los planes de ejecución en SQL Server. Una de las más sencillas es usar el Management Studio y activar la opción «Incluir plan de ejecución actual» o «Incluir plan de ejecución estimado» en el menú «Consulta». Esto nos mostrará el plan de ejecución junto con los resultados de la consulta. Otra forma es usar la instrucción SET SHOWPLAN_ON o SET SHOWPLAN_TEXT para obtener el plan de ejecución en formato textual. También podemos usar la función sys.dm_exec_query_plan para obtener el plan de ejecución en formato XML a partir del identificador de una consulta. Después, podemos usar esta web para ver gráficamente nuestros planes en XML.

¿Qué tipos de planes de ejecución hay?

Hay dos tipos principales de planes de ejecución: estimados y actuales. Los planes estimados se generan antes de ejecutar la consulta y se basan en las estadísticas disponibles y en el optimizador de consultas. Los planes actuales se generan después de ejecutar la consulta y reflejan lo que realmente ha ocurrido durante la ejecución. Nuestros planes estimados y actuales pueden ser diferentes si hay cambios en los datos, en las estadísticas o en el entorno. Por eso, es recomendable comparar ambos tipos de planes para detectar discrepancias y ajustar la consulta o las estadísticas si es necesario. 

¿Qué elementos componen un plan de ejecución? 

Un plan de ejecución se compone de varios elementos que representan los operadores lógicos y físicos que intervienen en la consulta. Cada elemento tiene un icono, un nombre y unas propiedades que describen su función y su coste. 

Algunos elementos comunes son: 
– Scan: lee todas las filas o páginas de una tabla o un índice. 
– Seek: busca una fila o un rango de filas en un índice usando una clave. 
– Join: combina filas de dos orígenes usando un criterio de unión. Hay varios tipos de join, como nested loops, merge o hash. 
– Aggregate: agrupa filas por una o más columnas y calcula funciones agregadas, como sum, count o avg. 
– Sort: ordena filas por una o más columnas. 
– Filter: elimina filas que no cumplen una condición. 
– Compute Scalar: calcula una expresión escalar, como una columna calculada o una función. 
– Stream Aggregate: agrupa filas por una o más columnas y calcula funciones agregadas usando un algoritmo eficiente que requiere que las filas estén ordenadas previamente. 
– Hash Match: realiza operaciones como join, aggregate o union usando una estructura hash para almacenar y buscar las filas. 
– Spool: almacena temporalmente las filas en el disco para reutilizarlas más adelante. 
– Top: devuelve un número fijo o porcentual de filas ordenadas por una o más columnas. 

Estos son solo algunos ejemplos, sin embargo, hay muchos más elementos que pueden aparecer en un plan de ejecución. Lo importante es entender qué hace cada elemento y cómo afecta al rendimiento de la consulta. Puedes encontrar la lista completa de operadores en la documentación oficial.

¿Cómo se interpreta un plan de ejecución? 

Para interpretar un plan de ejecución hay que tener en cuenta varios aspectos, como el orden, el coste, las estadísticas y las advertencias.
El orden indica la secuencia en la que se ejecutan los operadores. En un plan de ejecución gráfico, el orden se lee de derecha a izquierda y de arriba a abajo.
Por otro lado, el coste indica el porcentaje de recursos que consume cada operador respecto al total de la consulta y se puede ver en la barra de colores que acompaña a cada elemento. El coste es una estimación basada en las estadísticas y puede no reflejar el coste real.
Las estadísticas muestran información sobre las filas procesadas, el uso de memoria y CPU, el tiempo de ejecución, etc y se pueden ver en las propiedades de cada elemento o en el panel de detalles. Las estadísticas son datos reales que se obtienen al ejecutar la consulta.
Por último, las advertencias son mensajes que indican posibles problemas de rendimiento o de calidad de los datos. 

Advertencias

Las advertencias se pueden ver como símbolos de exclamación o de información en los elementos del plan. Como hemos visto, es importante que les prestemos atención ya que nos indican problemas en nuestras consultas.

Algunas advertencias comunes son: 
– Missing Index: indica que la consulta podría beneficiarse de un índice que no existe. 
– Sort Warnings: indica que la operación de ordenación ha usado más memoria de la disponible y ha tenido que escribir datos en el disco. 
– Hash Warnings: indica que la operación hash ha usado más memoria de la disponible y ha tenido que escribir datos en el disco. 
– Plan Affecting Convert: indica que hay una conversión implícita o explícita de tipos de datos que puede afectar al rendimiento o a la precisión de la consulta.
– SpillToTempDb: indica que una operación ha usado más memoria de la estimada y ha tenido que escribir datos en el disco. 

Estos son solo algunos ejemplos pero, hay muchas más advertencias que pueden aparecer en un plan de ejecución. Lo importante es prestar atención a las advertencias y corregir los problemas si es posible. 

Conclusión 

Los planes de ejecución son una herramienta imprescindible para cualquier DBA que quiera optimizar el rendimiento de las consultas en SQL Server. Los planes de ejecución nos permiten ver cómo SQL Server procesa una consulta y qué recursos consume cada operador. Con esta información, podemos identificar y resolver los problemas más comunes, como el uso inadecuado o la falta de índices, las estimaciones erróneas de cardinalidad, los operadores costosos o ineficientes, etc. Espero que este artículo os haya servido para introduciros en el mundo de los planes de ejecución y os anime a usarlos en vuestro trabajo diario. 

Si queréis profundizar más en este tema, os recomiendo los siguientes recursos:
– Los libros SQL Server Execution Plans y SQL Server Query Performance Tuning de Grant Fritchey
– Los post de Paul White en SQLPerformance.com
– Los cursos de Brent Ozar

Publicado por Roberto Carrancio en Rendimiento, SQL Server, 6 comentarios

Uso, llenado y mantenimiento de índices (Índices parte 7)

A lo largo de las pasadas 6 entradas hemos repasado todos los distintos tipos de índices en SQL Server. Sin embargo, hay aspectos que, aunque comentamos por encima en la introducción, no habíamos vuelto a profundizar y son los que vamos a ver. Durante estas líneas vamos a hablar sobre cuándo y cómo SQL Server hace uso de los índices. Además, veremos cómo se van llenando las páginas y cómo podemos influir nosotros en ello, un aspecto que afectará a la fragmentación. Por último aprenderemos cómo crear un plan de mantenimiento que solucione este problema.

Uso de índices y estadísticas

Sea cual sea el índice que creemos, SQL Server generará una estadística sobre él. De esta manera, podrá decidir si es recomendable buscar los registros a través de los niveles del  árbol B, leer directamente el nivel de hoja en busca de los registros o recorrer enteramente la tabla y no usar el índice. 

Cuando ejecutamos una consulta, SQL Server calculará distintos planes de ejecución con todas las opciones de recuperar los datos. En una tabla pequeña de menos de 10.000 páginas es fácil, se va a leer la tabla y no se usarán los índices nonclustered. Cuando tiene más, se usarán las estadísticas de los índices nonclustered para calcular el porcentaje de registros que se van a recuperar. En función de esos datos asignará un coste a cada uno de los planes de ejecución. Con todos los costes calculados se elegirá la mejor opción.

Llenado de índices y tablas

Hemos comentado en varias ocasiones que los datos en SQL se escriben en páginas. Vimos que en las tablas HEAP que los datos se escriben en el primer hueco libre disponible y, a la hora de editar, si el nuevo valor no cabe se mueve al final. Esto generaba punteros de reenvío y fragmentación.

En el caso de los índices clustered y nonclustered los datos se escriben en páginas igualmente. SQL Server intentará escribir el datos en su sitio pero, solo lo hará si hay sitio libre. Si no lo hay, lo escribirá en el espacio libre más próximo. 

Fill Factor

Dado este comportamiento, para reducir al máximo la fragmentación y aprovechar el espacio de manera óptima podemos definir en los índices un nivel de llenado o Fill Factor. Este Fill Factor es un porcentaje que indicará al SQL cuánto espacio de las páginas de los índices se va a llenar de datos (no aplica para tablas HEAP). Por tanto, podremos controlar cuánto espacio se va a dejar libre al final de las páginas.
Podremos definir un nivel genérico en las propiedades de la instancia además de uno particular para cada índice si no queremos que use el genérico. Si no definimos otra cosa el fill factor será del 100%, es decir SQL tratará de llenar completamente las páginas. Esto puede ser una buena idea para índices en campos incrementales que nunca se editan. Sin embargo, si este no es el caso, llenar completamente las páginas nos generará tener que mover los datos o no escribirlos donde toca. En resumen, un mayor consumo de E/S y por tanto peor rendimiento.

Mantenimiento de índices

Acabamos de ver que muy probablemente nuestros índices se van a fragmentar con el uso, así que, como DBAs, es imprescindible que tengamos una buena política de mantenimiento que garantice que se encuentren en óptimas condiciones. Para ello tenemos varias alternativas, reorganizarlos o reconstruirlos. El método elegido dependerá en gran medida de la fragmentación que tenga el índice al momento de optimizarlo. Como norma general, reorganizaremos los índices con menos de un 30% de fragmentación y reconstruiremos los más fragmentados.
Además, tendremos que implementar también un mantenimiento de las estadísticas, como hemos visto son claves para que el motor de bases de datos calcule los costes de los planes de ejecución y decida por cuál de ellos decantarse. La reconstrucción de índices conlleva el mantenimiento de estadísticas, pero la reorganización no, por lo que tendremos que hacerlo por separado. Como os comenté en el artículo de las tareas diarias de un DBA, yo me decanto por la solución de olla hallengren. 

Aunque un mantenimiento de las estadísticas es clave para el rendimiento de nuestra base de datos no tenemos que caer en el error de ejecutarlo con demasiada frecuencia.Cada vez que se actualizan estadísticas, todos los planes de ejecución que tenemos en la caché de SQL Server y que usan esas tablas se van a desechar. Esto provocará que la próxima ejecución de la consulta necesite recompilar el plan, calcular todas las opciones y elegir el de menos coste. Para esto, la solución de ola, tiene un parámetro para solo actualizar las estadísticas que hayan sufrido modificaciones desde la última actualización, lo que liberará a SQL Server de carga de trabajo innecesaria. 

Conclusión

Dado que con este artículo cerramos la serie de índices, permitidme que por una vez me extienda un poco más en la conclusión y recapitulemos todo lo visto. Primero de todo quiero daros las gracias a vosotros, habéis llevado las páginas de esta serie a las primeras posiciones de las estadísticas de visitas del blog en muy pocos días. 

Ahora ya sí, entremos en materia. Empezamos esta serie con una introducción donde vimos las estructura de las tablas en SQL Server y descubrimos la importancia de los índices.

Después analizamos los distintos tipos de índices que podemos usar en SQL Server, desde la ausencia de ellos en una tabla HEAP, índices clustered, nonclustered, columnares y, hasta los índices especiales (únicos, filtrados, espaciales, XML, hash y de texto completo). De todos ellos vimos cómo funcionan, para qué se usan y cuándo son recomendables.Todo este conocimiento adquirido durante los últimos días nos ha traído hasta esta última entrada donde hemos entendido mejor la fragmentación de los índices y cómo combatirla. Además hemos podido comprender la importancia de las estadísticas y su papel clave en el rendimiento de nuestro SQL Server. 

Seguro que los más expertos echaréis en falta algún que otro aspecto relacionado con los índices o que entremos más en detalle sobre alguno de los temas tratados, os prometo que habrá más artículos sobre índices en un futuro aunque sea ya fuera de esta serie. 
Como siempre, tenéis a vuestra disposición la sección de comentarios, mi mail y nuestra cuenta de twitter para cualquier aportación o sugerencia. 
¡Espero que os haya gustado esta serie y hayáis aprendido cosas nuevas!

Contenido de la serie

Introducción a los índices en SQL Server (Índices parte 1)

Estructura física de las tablas y HEAP (Índices parte 2)

Índices Clustered y Primary Keys (Índices parte 3)

Índices Nonclustered (Índices parte 4)

Índices Columnares (Índices parte 5)

Índices especiales (Índices parte 6)

Publicado por Roberto Carrancio en Índices, Rendimiento, SQL Server, 2 comentarios

Índices especiales (Índices parte 6)

Sexta entrega de esta amplia serie sobre índices donde hemos podido aprender en profundidad todo sobre los principales índices de SQL Server. Antes de entrar en detalle sobre el uso y mantenimiento de los mismos vamos a terminar de repasar rápidamente los tipos de índices especiales.Estos índices especiales son, en su mayoría, específicos para casos concretos de uso y, si bien su uso está mucho menos extendido, como DBAs tenemos que conocer. 

Índices especiales para funcionalidades comunes en SQL Server

Este tipo de índices están pensado para ampliar la funcionalidad de los índices existentes y que hemos repasado en anteriores entradas de esta serie. Hablamos de los índices únicos y los índices filtrados y podríamos decir que existen para complementar al resto de índices.

Índices únicos

La unicidad en una columna es una restricción que se utiliza para mantener la integridad de los datos. Como ya vimos cuando hablamos de las PK, una restricción es una medida lógica que necesita reforzarse con un índice físico. Siempre que queramos crear una restricción de unicidad se creará por defecto un índice nonclustered sobre esa(s) columna(s). También podremos añadir la cláusula UNIQUE al crear un índice clustered o nonclustered.

A la hora de borrarlos, tenemos que tener en cuenta que si la restricción de unicidad se ha creado como restricción (constraint). Si el índice se ha creado automáticamente para reforzar una constraint no podremos usar la sintaxis DROP INDEX y deberemos usar DROP CONSTRAINT. Si hemos creado un índice con la cláusula UNIQUE sí que deberemos usar DROP INDEX.

Índices Filtrados

En ocasiones creamos un índice para mejorar el rendimiento de unas consultas que siempre filtran por los mismos valores de una columna. En estos casos podremos agregar un predicado where a nuestros índices nonclustered (columnstore o árbol B). Esto reducirá el espacio que ocupa nuestro índice y mejorará su rendimiento. 

Tenemos que tener en cuenta que cuanto más lógica añadamos a nuestros índices más le costará a SQL Server mantenerlos. Si el conjunto de resultados resultantes de los filtros del índice, es similar al total de la tabla seguramente no será rentable mantenerlo y deberemos crear un índice de tabla-completa.

Índices especiales para columnas especiales en SQL Server

A lo largo de las distintas versiones de SQL se ha ido añadiendo soporte para información especial. Estos índices que vamos a ver a continuación están pensados para dar soporte a este tipo de información especial.

Índices espaciales

A partir de SQL Server 2008 se introdujeron las columnas GEOGRAPHY y GEOMETRY, para guardar información de coordenadas o geométrica de puntos, líneas y polígonos respectivamente. Estos tipos de datos se conocen como datos espaciales y para ellos se crearon estos tipos de índices. 

Estos índices solo se pueden crear en tablas que tengan PK y en columnas espaciales. 

Índices XML

Como su propio nombre indica los índices XML son índices especiales para columnas de tipo XML en tablas que tengan PK. A poco que conozcáis el estándar XML entenderéis que estas columnas normalmente almacenan grandes cantidades de información de distintos tipos agrupadas en categorías. 

Existen dos tipos de índices XML, los primarios y los secundarios. Crearemos un índice XML primario para indexar las columnas XML. Después de crear el índice primario podremos crear índices XML secundarios para etiquetas concretas dentro de esa columna.

Índices especiales para características especiales de SQL Server

Igual que nos ha pasado en el apartado anterior, SQL ha ido implementando nuevas características para las que ha implementado los índices concretos que vamos a ver ahora. 

Índices hash

Son un tipo de índice especial para tablas optimizadas para memoria. Todas las tablas optimizadas para memoria requieren de un índice de este tipo o un índice nonclustered optimizado para memoria. Sin entrar mucho más en detalle, tenemos que saber que son un tipo de índice que almacena hash de la información ocupando una cantidad de memoria fija.

Índices de texto completo

Este tipo de índices son creados, usados y mantenidos por el motor de SQL Full-text. Permiten a las consultas Full-Text localizar cadenas de texto, para ello dividen las columnas en tokens que luego almacenan en el índice. 

Conclusión

Concluimos con esta sexta entrega el repaso a los distintos tipos de índices que nos podemos encontrar en SQL Server. Hoy hemos aprendido cuáles son los índices especiales que se adaptarán a casuísticas concretas de nuestras instalaciones. Seguid atentos al blog que a partir de mañana empezaremos a ver aspectos más prácticos sobre este mismo tema. Y ya para cerrar lo que os digo siempre, animaos a dejarme vuestras dudas y comentarios aquí debajo o en twitter o por mail.

Contenido de la serie

Introducción a los índices en SQL Server (Índices parte 1)

Estructura física de las tablas y HEAP (Índices parte 2)

Índices Clustered y Primary Keys (Índices parte 3)

Índices Nonclustered (Índices parte 4)

Índices Columnares (Índices parte 5)

Publicado por Roberto Carrancio en Índices, Rendimiento, SQL Server, 6 comentarios

Índices Columnares (Índices parte 5)

Quinta entrega de nuestra serie de índices y aún nos queda mucho que ordenar. Después de haber visto los tipos de índices más comunes, hoy nos vamos a adentrar en los índices columnares, un tipo de índice no tan conocido pero muy usado en entornos OLAP y datawarehouse. Esto no es casualidad, veremos que, por sus características, son ideales para almacenar la información de las tablas de hechos en entornos donde se ejecutan gran cantidad de consultas complejas. 

Estructura de los índices columnares

Como hemos estado viendo hasta ahora los datos en SQL se almacenan en páginas de 8Kb donde vamos a guardar la información fila a fila de nuestra tabla. Cada página contiene N filas completas, es decir todas sus columnas (excepto las grandes que se almacenan en páginas LOB o ROW_OVERFLOW). Con los índices columnares vamos a dejar este paradigma y vamos a ver que, gracias a la tecnología de compresión VertiPaq, la información se almacena por columnas y no por filas. Tampoco vamos a hablar de páginas sino de segmentos. Una columna tendrá uno o más segmentos de datos. Aunque cambiemos el término tenéis que saber que un segmento es una página que almacena datos de una única columna.
Esto nos va a permitir reducir la E/S en gran medida al leer solo algunas columnas de la tabla ya que no vamos a tener que recuperar las filas completas de datos para luego mostrar unas cuantas columnas. Otra de las ventajas que tiene es que permite comprimir los datos. Normalmente los datos de una columna suelen repetirse y los índices columnares se aprovechan de eso escribiendo el dato solo una vez y añadiendo punteros a ese valor el resto de las veces. Esto se traduce en unas 10 veces menos consumo de disco y unas 10 veces mejor rendimiento en consultas pesadas.

Tipos de índices columnares

Al igual que en los índices de árbol B vamos a tener índices columnares clustered y nonclustered. Cada uno de ellos será ideal para un escenario concreto como vamos a ver a continuación. 

Índice columnar clustered

Al igual que con los índices de almacenamiento de filas (o de árbol B) este índice columnar clustered convierte toda nuestra tabla a almacenamiento de columnas. Sin embargo, no todos nuestros datos se almacenan en formato columnar, hasta que no tengamos aproximadamente un millón de filas los datos se almacenarán en un grupo de filas delta. 

Los grupos de filas delta (almacén delta) son estructuras de árbol B especialmente diseñadas para trabajar con almacén de columnas. Cuando un grupo de filas delta llega a 1.048.576 filas pasa de estado OPEN a CLOSED. En ese momento, un proceso en segundo plano llamado motor de tupla comprime el contenido y lo copia al almacén columnar. El grupo de filas en el almacén columnar pasa a estado COMPRESSED y el grupo del almacén delta se marca para borrar, estado TOMBSTONE (muy poético todo).

Hay una excepción a todo esto que hemos visto y son las cargas masivas o bulk insert. Cuando se produce una carga masiva sobre una tabla con un índice columnar clustered las filas van directamente al almacén de columnas. Se van guardando en grupos CLOSED de un millón de filas. Al finalizar, si el último grupo de filas tiene menos del mínimo de filas permitido (102.400) el grupo se moverá a un almacén delta.
Si la carga masiva directamente es inferior a ese valor mínimo se cargará directamente en un almacén delta. 

Índice columnar nonclustered

No nos vamos a extender mucho, un índice columnar nonclustered funciona igual que un índice columnar clustered. La diferencia es que se almacena separado de la tabla y puede contener desde solo una columna hasta todas las de la tabla. 

Podemos crear un índice columnar nonclustered sobre tablas de árbol B (almacenamiento clásico de filas) lo que nos permite aprovecharnos de las ventajas de estos índices sin sus inconvenientes.

Cuándo elegir un índice columnar

Acabo de deciros que los índices columnares tienen inconvenientes, pero ¿cuáles son? Hasta ahora hemos visto que un índice columnar reduce el consumo de disco y mejora el rendimiento en consultas pesadas. Gracias a su estructura están especialmente diseñados para funciones de agregación como sumas, cálculos de valores promedio y en general cualquier operación que implique trabajar con datos de una sola columna. Sin embargo, esto que los hace buenos en entornos OLAP los hace especialmente malos en búsquedas de un valor concreto típicas de un entorno transaccional (OLTP). 

Conclusión

Hemos aprendido qué son y cómo se organizan los índices columnares. También podemos entender cuándo debemos usarlos. Para las tablas de hechos de nuestro datawarehouse lo mejor son los índices columnares clustered mientras que para la mayoría de entornos transaccionales no nos serán de ayuda. Sin embargo, en la vida real es común que los departamentos de análisis, además de trabajar sobre sus entornos OLAP, realicen consultas en caliente sobre entornos OLTP y es donde más partido podremos sacar de los índices columnares nonclustered. También para informes de las propias aplicaciones, no tenemos por qué irnos a informes de departamentos específicos.

Publicado por Roberto Carrancio en Índices, Rendimiento, SQL Server, 7 comentarios