Incidencias

Código dinámico con seguridad en SQL Server

En muchas ocasiones nos enfrentamos a escenarios donde necesitamos construir sentencias SQL de forma dinámica. Ya sea para crear filtros condicionales, construir cláusulas ORDER BY en tiempo de ejecución o ejecutar consultas sobre distintos objetos, la generación de SQL dinámico parece una solución sencilla y flexible. Pero esta potencia viene acompañada de riesgos, especialmente desde el punto de vista de la seguridad.

A lo largo de los años, he visto cómo el uso descuidado del SQL dinámico ha sido uno de los vectores de ataques de inyección SQL (SQLi) más comunes, y aún sigue siéndolo. Por eso, en este artículo vamos a repasar cómo generar SQL dinámico en SQL Server de forma segura, analizando técnicas recomendadas y errores frecuentes, con ejemplos claros y aplicables.

¿Cuándo usamos SQL dinámico?

Los escenarios más frecuentes en los que aparece la necesidad de SQL dinámico suelen estar relacionados con filtros condicionales, búsquedas avanzadas, generación de informes personalizables, lógica multi-tenant o incluso mantenimiento automatizado.

Un ejemplo muy habitual es una búsqueda con varios filtros opcionales. Supongamos una aplicación que consulta una tabla de personas donde el usuario puede buscar por nombre, ciudad y país. Si tratamos de resolver esto con un procedimiento estándar, el número de combinaciones posibles puede crecer exponencialmente. El SQL dinámico permite construir la sentencia ajustada a los filtros que el usuario haya proporcionado.

El problema del SQL Injection

El gran riesgo del SQL dinámico mal implementado es el conocido SQL Injection, una técnica con la que un atacante puede alterar la consulta ejecutada para acceder o modificar datos sin autorización. Esto ocurre cuando concatenamos directamente valores dentro de la cadena SQL. Veamos un ejemplo inseguro:

Si @city proviene de un parámetro externo (una app, una web), el usuario podría inyectar algo como: “Madrid’; DROP TABLE Person.Person;” y provocar un desastre.

Este patrón, por desgracia, sigue viéndose demasiado a menudo en aplicaciones heredadas o mal diseñadas.

Uso seguro de SQL dinámico con sp_executesql

La solución más eficaz ante este problema es usar sp_executesql, que permite construir consultas dinámicas pero separando el código de los datos mediante parámetros tipados. Esto bloquea cualquier intento de inyección porque el valor del parámetro no se interpreta como código. Reescribamos el ejemplo anterior de forma segura:

Aquí, aunque el usuario intentase inyectar código en @city, no lo conseguiría. SQL Server lo tratará como un valor, no como una parte de la instrucción.

Además, esta técnica también permite la reutilización de planes de ejecución, lo que supone una ventaja adicional en términos de rendimiento.

Código dinámico con filtros condicionales

Un paso más allá es cuando necesitamos construir dinámicamente múltiples filtros. En estos casos, lo ideal es ir concatenando las condiciones SQL pero parametrizando todos los valores. Veamos un ejemplo más completo:

De este modo, solo se añaden los filtros que tengan valor, pero todos los valores siguen protegidos mediante parámetros.

Identificadores dinámicos: el caso más delicado

sp_executesql no permite parametrizar nombres de columnas o tablas. Esto es especialmente importante si necesitamos cambiar el objeto sobre el que se ejecuta la consulta. En estos casos debemos concatenar el identificador, pero asegurándonos de que el valor es válido. La función QUOTENAME es clave para evitar inyecciones sobre identificadores ya que introduce el nombre del objeto entre corchetes [].

Aquí estamos asumiendo que el nombre de la tabla ha sido validado previamente. Aún así, QUOTENAME evita que un valor como SalesOrderHeader; DROP TABLE x;– pueda hacer daño. 

En entornos multi-tenant esto es especialmente útil si cada cliente tiene su propia tabla (modelo database-per-tenant) y accedemos a ellas dinámicamente.

Consejos adicionales para SQL dinámico seguro

Cuando usamos SQL dinámico en entornos críticos o expuestos a usuarios externos, es fundamental aplicar otras prácticas complementarias como encapsular en procedimientos almacenados ya que así reducimos la exposición del motor y permitimos auditar más fácilmente. Otra buena práctica es registrar las consultas generadas, esto es especialmente útil para soporte, auditoría y detección de patrones de abuso. Otro paso obligatorio, para mi es evitar privilegios excesivos; el usuario que ejecuta el código dinámico no debe tener más permisos de los necesarios.

Por último, si queremos ir un paso más allá, podemos aplicar SET FMTONLY OFF y otras opciones de seguridad sobre todo si trabajamos con herramientas de terceros. De esta manera podremos asegurarnos de que el motor de base de datos ejecuta todo el bloque tal cual, sin modificar el flujo por culpa del modo de metadatos.

Conclusión

El SQL dinámico en SQL Server puede ser tan útil como peligroso. En nuestras manos está la diferencia entre construir una solución flexible y robusta o abrir una puerta trasera a posibles ataques.

La clave está en nunca concatenar valores directamente y utilizar sp_executesql con parámetros siempre que sea posible. Cuando se trabaja con nombres de objetos, debemos validar y proteger con QUOTENAME. Y si el contexto lo permite, encapsular toda la lógica dentro de procedimientos controlados.

Estas técnicas yo las aplico habitualmente en proyectos reales, y son parte esencial de una arquitectura segura, especialmente en entornos donde la escalabilidad o la “multi-tenencia” requieren cierta flexibilidad a nivel de metadatos. 

Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de Telegram y un canal de YouTube a los que te puede unir. ¡Hasta la próxima! 

Publicado por Roberto Carrancio en Cloud, SQL Server, 1 comentario

Crecimiento del Persistent Version Store (PVS) en SQL Server

En el día a día de la administración de bases de datos SQL Server, nos encontramos con diversas funcionalidades que buscan optimizar el rendimiento y la disponibilidad de nuestros sistemas. Una de ellas, la Recuperación Acelerada de Bases de Datos (ADR), introdujo un concepto fundamental para la gestión de transacciones y la recuperación ante fallos: el Persistent Version Store (PVS). Aunque el ADR nos brinda notables ventajas en cuanto a la rapidez de las reversiones de transacciones y la disponibilidad de la base de datos, su componente central, el PVS, puede convertirse en una fuente de preocupación si no comprendemos su funcionamiento y cómo controlar su tamaño. En este artículo, profundizaremos en qué consiste el PVS, cómo impacta en el espacio de almacenamiento de nuestras bases de datos y, lo más importante, qué estrategias podemos implementar para mantenerlo bajo control. 

¿Qué es el Persistent Version Store (PVS) de SQL Server y cómo funciona?

El Persistent Version Store (PVS) es un mecanismo introducido con la característica de Accelerated Database Recovery (ADR) en SQL Server. Su principal función es almacenar las versiones antiguas de las filas que han sido modificadas por transacciones aún activas o recientemente completadas. Esta estrategia difiere significativamente del comportamiento tradicional de SQL Server sin ADR, donde los valores antiguos se guardan en el registro de transacciones. Con ADR, al modificar una fila, SQL Server escribe una nueva versión de dicha fila en la misma tabla, manteniendo la versión anterior intacta en el PVS. Esta arquitectura permite que las reversiones de transacciones sean prácticamente instantáneas, ya que el motor no necesita recuperar y aplicar información desde el registro de transacciones.

Es crucial entender que el PVS reside dentro de la propia base de datos de usuario, específicamente en los mismos archivos de datos (.mdf). Esta ubicación contrasta con el almacén de versiones utilizado por el Read Committed Snapshot Isolation (RCSI) cuando ADR no está habilitado, el cual se crea y mantiene en la base de datos del sistema TempDB. Aunque ambos mecanismos se basan en el versionado de filas para ofrecer lecturas consistentes sin bloqueos, la persistencia del PVS dentro de la base de datos de usuario tiene implicaciones directas en su tamaño y gestión.

¿Cómo influye el PVS en el tamaño de nuestra base de datos?

La implementación del PVS tiene un impacto directo en el consumo de espacio de nuestras bases de datos. Al almacenar múltiples versiones de las filas modificadas, es inevitable que el tamaño de la base de datos en disco aumente para albergar estas versiones. De hecho, al habilitar ADR, incluso antes de realizar modificaciones, se observa un aumento inicial en el tamaño de la tabla en comparación con una tabla idéntica sin ADR. Esto se debe a que ADR necesita añadir una marca de tiempo a cada fila para rastrear sus versiones.

Además del espacio ocupado por las versiones de las filas en sí, cada fila de la tabla con ADR habilitado contiene un puntero de 14 bytes que apunta a la ubicación de su versión en el PVS, incluso si la fila no ha sido modificada recientemente. Este overhead por fila es un factor significativo que contribuye al aumento del tamaño de la base de datos. Es importante señalar que este mismo puntero existe incluso cuando solo RCSI está habilitado (sin ADR), aunque en ese caso apunte al almacén de versiones ubicado en TempDB. Por lo tanto, el crecimiento en la tabla de usuario debido a este overhead es similar tanto con ADR como con RCSI. Hablamos de esto aquí.

Los experimentos han demostrado que, tras la carga inicial de datos en tablas con ADR y/o RCSI activados, estas tienden a ser más grandes que las tablas sin estas funcionalidades. Este crecimiento se acelera considerablemente al realizar actividad de escritura, llegando incluso a duplicarse el tamaño de los objetos tras la primera actualización en bases de datos con ADR y/o RCSI habilitados. Esta tendencia se mantiene con rondas sucesivas de actualizaciones, donde las bases de datos con versionado de filas experimentan un crecimiento mucho más rápido que aquellas sin él.

El PVS y el mito de la reconstrucción de índices para ahorrar espacio

Ante este crecimiento acelerado de las tablas con ADR y RCSI, una reacción común podría ser recurrir a la reconstrucción de índices como una solución para recuperar el espacio aparentemente «perdido». Efectivamente, al reconstruir los índices en estas tablas, se observa una reducción drástica en su tamaño, igualando incluso el tamaño de tablas sin ADR. Esto podría generar la ilusión de haber «ahorrado» espacio en disco.

Sin embargo, esta ganancia de espacio es puramente ilusoria y temporal. Tan pronto como la carga de trabajo habitual se reanuda y se realizan nuevas actualizaciones, el tamaño de las tablas con ADR y RCSI vuelve a inflarse rápidamente. Nos encontramos, por lo tanto, en un ciclo continuo de crecimiento y reconstrucción sin abordar la causa fundamental del aumento de tamaño: el versionado de filas necesario para el funcionamiento de ADR y RCSI.

La reconstrucción de índices simplemente reorganiza los datos y elimina las versiones antiguas que ya no son necesarias en el momento de la reconstrucción, pero no impide la generación de nuevas versiones con futuras modificaciones. Por lo tanto, si nuestro principal objetivo al reconstruir índices en un entorno con ADR o RCSI es ganar espacio en disco, debemos comprender que este ahorro será efímero. El espacio «ahorrado» volverá a ser necesario a medida que se generen nuevas versiones de las filas. En lugar de centrarnos en la reconstrucción como una panacea para el espacio, debemos enfocarnos en dimensionar adecuadamente nuestro almacenamiento y comprender las implicaciones del versionado de filas en el crecimiento de nuestras bases de datos.

Estrategias efectivas para controlar el tamaño del PVS

Dado que la reconstrucción de índices no es una solución sostenible para controlar el tamaño del PVS, ¿qué alternativas tenemos a nuestra disposición? La clave reside en comprender la naturaleza del PVS y cómo interactúa con la actividad de nuestra base de datos.

En primer lugar, es fundamental realizar un dimensionamiento adecuado del almacenamiento. Si habilitamos ADR, debemos ser conscientes del potencial crecimiento adicional que experimentará nuestra base de datos debido al almacenamiento de las versiones de filas y al overhead por fila. Ignorar este aspecto puede llevarnos rápidamente a situaciones de falta de espacio en disco.

En segundo lugar, la monitorización activa del tamaño del PVS es esencial. SQL Server nos proporciona herramientas para observar el comportamiento del PVS y detectar patrones de crecimiento inusuales. Mediante el uso de Dynamic Management Views (DMVs), como sys.dm_tran_persistent_version_store_stats, podemos obtener información valiosa sobre el tamaño actual del PVS, el porcentaje que representa del tamaño total de la base de datos, el número de transacciones abortadas y la antigüedad de las transacciones activas.

Además de la monitorización del tamaño, es importante analizar la actividad de la base de datos. Cargas de trabajo con una alta intensidad de escritura generarán más versiones de filas y, por lo tanto, un mayor crecimiento del PVS. Asimismo, las transacciones de larga duración pueden impedir la limpieza de las versiones antiguas, contribuyendo a un aumento sostenido del tamaño del PVS. Identificar y optimizar estas transacciones puede tener un impacto significativo en la gestión del espacio del PVS.

Monitorizando el Persistent Version Store

Terminar u optimizar las transacciones de larga duración se convierte en una práctica crucial. Si una transacción permanece abierta durante un tiempo prolongado, las versiones de las filas modificadas tanto por esa transacción como por todas las transacciones siguientes no podrán ser limpiadas del PVS, lo que provocará su crecimiento descontrolado.

Como mencionaba anteriormente, las DMVs son nuestras principales aliadas para supervisar el PVS. La DMV sys.dm_tran_persistent_version_store_stats nos ofrece una visión detallada del estado del almacén de versiones persistente. Algunas de las columnas más relevantes que podemos consultar son:

  • persistent_version_store_size_kb: Indica el tamaño actual del PVS en kilobytes.
  • oldest_active_transaction_id: El ID de la transacción activa más antigua.
  • oldest_transaction_begin_time: La hora de inicio de la transacción activa más antigua.

Podemos utilizar la siguiente consulta (de la documentación oficial) para obtener una visión general del PVS en nuestras bases de datos habilitadas para ADR:

Esta consulta nos proporciona información crucial para entender el consumo de espacio del PVS y la antigüedad de las transacciones activas, lo que nos puede ayudar a identificar posibles cuellos de botella o transacciones problemáticas que estén impidiendo la limpieza del almacén de versiones.

También es útil esta otra consulta (misma fuente) para obtener las consultas que pueden ser un potencial problema. Está parametrizada por defecto para localizar las transacciones de más de 15 minutos de duración o 1 Gb de log de transacciones, lo que ya empieza a ser preocupante y puede tener un impacto en el tamaño del PVS.

Conclusión

La introducción del Persistent Version Store (PVS) con la Recuperación Acelerada de Bases de Datos (ADR) representa un avance significativo en la forma en que SQL Server gestiona las transacciones y la recuperación. Sin embargo, como hemos explorado, su funcionamiento basado en el versionado de filas tiene implicaciones directas en el tamaño de nuestras bases de datos. La idea de que la reconstrucción de índices sea una solución efectiva para controlar este tamaño ha demostrado ser una ilusión temporal.

En lugar de recurrir a prácticas de mantenimiento obsoletas, debemos adoptar un enfoque más informado y proactivo. Esto implica dimensionar adecuadamente nuestro almacenamiento, monitorizar activamente el tamaño del PVS mediante las DMVs proporcionadas por SQL Server y, fundamentalmente, comprender y optimizar la actividad de nuestras bases de datos, prestando especial atención a las transacciones de larga duración.

En definitiva, la gestión exitosa del PVS requiere que evolucionemos nuestras rutinas de mantenimiento y nos centremos en comprender el mecanismo subyacente del versionado de filas. Solo así podremos tomar decisiones informadas y evitar invertir tiempo y recursos en acciones que nos ofrecen solo una sensación temporal de mejora, asegurando un rendimiento óptimo y una gestión eficiente del espacio en nuestras bases de datos SQL Server.

Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de Telegram y un canal de YouTube a los que te puede unir. ¡Hasta la próxima! 

Publicado por Roberto Carrancio en Cloud, SQL Server, 0 comentarios
¿Reconstruir índices? Quizá ya no tiene tanto sentido como pensábamos

¿Reconstruir índices? Quizá ya no tiene tanto sentido como pensábamos

Durante años, una de las tareas de mantenimiento más comunes en nuestros servidores SQL Server ha sido la reconstrucción de índices. La idea de eliminar la fragmentación, mejorar el rendimiento de las consultas y, en ocasiones, recuperar espacio en disco ha estado firmemente arraigada en nuestras rutinas. Sin embargo, la evolución de SQL Server con la introducción de características como Accelerated Database Recovery (ADR) y Read Committed Snapshot Isolation (RCSI) nos obliga a replantearnos si esta práctica sigue teniendo el mismo sentido que antes. En este artículo, basándonos en un experimento que hice recientemente, veremos cómo estas nuevas funcionalidades impactan en la necesidad de reconstruir índices y por qué, en muchos casos, puede que estemos invirtiendo tiempo y recursos de forma innecesaria.

¿Reconstruir índices con ADR? Un nuevo paradigma en la recuperación

Para entender por qué la reconstrucción de índices podría ser menos relevante con ADR, primero debemos recordar cómo funciona esta característica. Sin ADR, cuando modificamos una fila, SQL Server guarda los valores antiguos en el registro de transacciones y actualiza la fila directamente. Si la transacción se revierte, el motor debe recuperar los valores antiguos del registro y aplicarlos de nuevo a la fila. Cuantas más filas se hayan modificado, más tiempo tardará la reversión.

Con ADR, esta operativa cambia radicalmente. En lugar de sobrescribir la fila original, SQL Server escribe una nueva versión de la fila dentro de la misma tabla, manteniendo la versión antigua intacta. Esta estrategia permite que las reversiones de transacciones sean casi instantáneas, ya que no es necesario leer y aplicar información del registro de transacciones.

Como seguramente ya habréis imaginado, almacenar múltiples versiones de una misma fila en la tabla tiene un impacto directo en el consumo de espacio. Para demostrarlo, hace unos días realicé una prueba creando dos bases de datos idénticas, una con ADR habilitado (Test_ADR) y otra sin él (Test), y cargué ambas con un millón de filas en tablas con la misma estructura. Inicialmente, como era de esperar, la tabla con ADR activado (Products_ADR) ocupó más espacio que la tabla normal (Products). Esto se debe a que, de forma similar a RCSI, ADR necesita añadir una marca de tiempo a cada fila para rastrear sus versiones.

¿Reconstruir índices con ADR y RCSI? Un experimento revelador

La primera sorpresa llegó al reconstruir los índices en ambas tablas. Tras la reconstrucción, el tamaño de la tabla Products_ADR, que inicialmente era mayor, se redujo drásticamente hasta igualar el tamaño de la tabla Products. Esto nos plantea una pregunta intrigante: si ADR ya estaba activo al cargar los datos, ¿por qué la reconstrucción de índices liberó tanto espacio? Se podría pensar que las marcas de tiempo de versionado deberían haberse insertado con los datos iniciales, sin causar una fragmentación excesiva.

Repetí este experimento varias veces, incluso en bases de datos con ADR y RCSI activados simultáneamente, y los resultados fueron consistentes. Después de la carga inicial de datos, las tablas con ADR y/o RCSI tendían a ser más grandes. Sin embargo, tras una reconstrucción de índices, todos los tamaños se normalizaban.

La verdadera diferencia se hizo evidente al simular actividad de escritura. Al actualizar un 10% de las filas en todas las tablas, observamos que en la base de datos “normal”, el tamaño de los objetos se mantenía relativamente estable, con un ligero aumento en el índice no clúster de la columna actualizada. Esto es comprensible, ya que las filas modificadas podrían necesitar moverse a nuevas páginas para mantener el orden del índice. No obstante, en las bases de datos con ADR y/o RCSI habilitados, el tamaño de los objetos explotó, llegando casi a duplicarse tras la primera actualización. Al realizar más rondas de actualizaciones, la tendencia se mantuvo: mientras que la base de datos sin ADR crecía de forma gradual, las bases de datos con ADR y RCSI experimentaban un crecimiento mucho más rápido.

¿Por qué crecen las bases de datos con el versionado de filas?

El crecimiento del tamaño de las bases de datos al habilitar funcionalidades como ADR (Accelerated Database Recovery) y RCSI (Read Committed Snapshot Isolation) se debe al mecanismo de versionado de filas, que permite lecturas consistentes sin bloqueos. Sin embargo, aunque la ubicación del almacén de versiones sea la TempDB como con RCSI existe un overhead por fila que explica este aumento de tamaño. 

Cuando ADR está habilitado

La Recuperación Acelerada de Bases de Datos utiliza un almacén de versiones persistente (PVS) que se encuentra dentro de la propia base de datos de usuario. Esto significa que las versiones anteriores de las filas modificadas se almacenan en el mismo archivo de datos (.mdf) de la base de datos. Como resultado directo, el tamaño de la base de datos en disco aumenta para albergar estas versiones.

Adicionalmente, cada fila de la tabla contendrá un puntero de 14 bytes que apunta a la ubicación de su versión en el PVS, incluso si la fila no ha sido modificada recientemente. Este overhead por fila es el principal causante del aumento del tamaño de la base de datos.

Cuando RCSI está habilitado (sin ADR)

Si la base de datos tiene habilitado el aislamiento por instantánea de lectura confirmada (RCSI) pero no la Recuperación Acelerada de Bases de Datos (ADR), el almacén de versiones se crea y se mantiene en la base de datos del sistema TempDB. Esto significa que las versiones de las filas modificadas en la base de datos de usuario se almacenan temporalmente en TempDB. Por lo tanto, podriamos pensar que la base de datos de usuario en sí misma debería no experimentar un aumento tan drástico debido al almacenamiento de las versiones, aunque TempDB sí crecerá para acomodar estas versiones.

Sin embargo, al igual que con ADR, cada fila de la tabla en la base de datos de usuario seguirá teniendo el puntero de 14 bytes que apunta al almacén de versiones, aunque en este caso, el almacén esté ubicado en TempDB. Este overhead por fila en la base de datos de usuario hace que el crecimiento que veamos en la tabla sea igual que en las que están en una base de datos con ADR.

Otras funcionalidades afectadas por el versionado de filas

Además de ADR y RCSI que, como acabamos de ver, usan un almacén de versiones, hay más funcionalidades de SQL que lo necesitan. En concreto, las más comunes son las bases de datos secundarias legibles en configuraciones Always On que emplean un almacén de versiones para ofrecer lecturas consistentes en la réplica secundaria.

Otra característica son las vistas indexadas que utilizan el versionado de filas para mantener la consistencia y los Triggers AFTER UPDATE que pueden depender del versionado de filas para acceder a los estados anteriores de las filas modificadas.

En resumen, el crecimiento de las bases de datos con el versionado de filas se debe tanto al almacenamiento de las versiones anteriores de las filas en sí (dentro de la base de datos con ADR, o en TempDB con RCSI) como al overhead de un puntero de 14 bytes añadido a cada fila en la base de datos de usuario para referenciar este almacén de versiones. Es crucial tener en cuenta estas implicaciones de almacenamiento al planificar la implementación de estas funcionalidades.

¿Reconstruir índices para ahorrar espacio? Una ilusión temporal

Ante este crecimiento acelerado de las tablas con ADR y RCSI, la reacción natural sería pensar en la reconstrucción de índices como una solución para recuperar el espacio «perdido». Y, efectivamente, al reconstruir los índices en estas tablas infladas, su tamaño volvía a los valores iniciales, dando la sensación de haber «ahorrado» espacio en disco.

Sin embargo, esta ganancia de espacio es puramente ilusoria y temporal. En cuanto la carga de trabajo habitual se reanudaba y se volvían a realizar actualizaciones, el tamaño de las tablas con ADR y RCSI volvía a inflarse rápidamente. Nos encontrábamos en un ciclo sin fin de crecimiento y reconstrucción, sin abordar la causa fundamental del aumento de tamaño.

La clave para entender esta dinámica reside en la forma en que ADR y RCSI gestionan el versionado de filas. Al mantener las versiones antiguas de las filas modificadas, es inevitable que el espacio ocupado por la tabla crezca con la actividad de escritura. La reconstrucción de índices simplemente reorganiza los datos y elimina las versiones antiguas que ya no son necesarias en el momento de la reconstrucción, pero no evita que se generen nuevas versiones con futuras modificaciones. Por lo tanto, si nuestro objetivo es «ahorrar» espacio mediante la reconstrucción de índices en un entorno con ADR o RCSI, debemos entender que este ahorro será efímero. El espacio «ahorrado» volverá a ser necesario a medida que se generen nuevas versiones de las filas.

¿Reconstruir índices como en 2005? Los tiempos cambian

Esta observación nos lleva a una reflexión importante sobre nuestras prácticas de mantenimiento. Si seguimos reconstruyendo índices como si estuviéramos en 2005, pensando que estamos logrando una mejora significativa en términos de espacio en disco y rendimiento, es hora de detenernos y reconsiderar nuestra estrategia. Las mejores prácticas evolucionan con los nuevos avances de la tecnología.

La evolución de las mejores prácticas nos indica que la obsesión por la utilización del espacio en disco a menudo nos lleva a tratar los síntomas, como la hinchazón de las tablas, en lugar de la causa subyacente, que en entornos con ADR y RCSI es el versionado de filas necesario para su funcionamiento. Reconstruir índices regularmente en estos entornos puede ser una solución ilusoria para el espacio , ya que el espacio ganado se volverá a utilizar rápidamente a medida que la carga de trabajo genere nuevas versiones de las filas. 

Incluso podría ser contraproducente a largo plazo si se realiza sin una justificación real de mejora del rendimiento, especialmente considerando la menor penalización por fragmentación en unidades de estado sólido (SSD), que ofrecen tiempos de acceso aleatorio mucho más rápidos que los discos duros tradicionales (HDD). Además en entornos con almacenamiento virtualizado, la contigüidad física de los datos es aún menos común y tiene menos relevancia la fragmentación de los índices.

Casos donde la reconstrucción sí tiene sentido 

Existen casos específicos donde la reconstrucción sí tiene sentido, pero son menos comunes. Por ejemplo, cuando se insertan inicialmente filas con muchos valores nulos que posteriormente se actualizan y ya no se modifican. En estos casos una reconstrucción podría compactar las páginas y liberar espacio que ya no es necesario. Sin embargo, en la mayoría de los escenarios con ADR o RCSI habilitados, si nuestro principal objetivo al reconstruir índices es ganar espacio en disco, las ganancias serán en gran medida temporales e insignificantes. Debemos enfocarnos en el problema real que estamos tratando de resolver: ¿es el espacio en disco o el rendimiento de las consultas? En muchos casos, ADR y RCSI están diseñados para mejorar la concurrencia y la disponibilidad, lo que podría reducir la necesidad de reconstrucciones de índices frecuentes con fines de rendimiento, especialmente en combinación con un hardware de almacenamiento adecuado.

Conclusión

Los experimentos que he realizado nos muestran claramente que la reconstrucción de índices en bases de datos con ADR y/o RCSI activados tiene un impacto diferente al que estábamos acostumbrados. Si bien inicialmente puede parecer que recuperamos espacio en disco, este ahorro es fugaz, ya que la propia naturaleza del versionado de filas hará que las tablas vuelvan a crecer con la actividad de escritura.

Es fundamental que nosotros, como profesionales de bases de datos, comprendamos a fondo cómo funcionan estas nuevas características y cómo impactan en nuestras tareas de mantenimiento. En lugar de seguir ciegamente las prácticas del pasado, debemos analizar el problema real que intentamos resolver. Si el aumento de tamaño de nuestras tablas es una consecuencia directa del versionado de filas necesario para ADR y RCSI, quizás la solución no sea reconstruir índices constantemente, sino dimensionar adecuadamente nuestro almacenamiento y enfocar nuestros esfuerzos en otras áreas de optimización.

En definitiva, la llegada de ADR y RCSI nos invita a replantearnos nuestras rutinas de mantenimiento de índices. Entender el mecanismo subyacente del versionado de filas es crucial para tomar decisiones informadas y evitar invertir tiempo y recursos en acciones que nos ofrecen solo una sensación temporal de mejora. La evolución de SQL Server nos exige una evolución en nuestra forma de gestionarlo.

Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de Telegram y un canal de YouTube a los que te puede unir. ¡Hasta la próxima! 

Publicado por Roberto Carrancio en Cloud, Índices, Rendimiento, SQL Server, 0 comentarios
¿Por qué necesitas usar AUTO_DROP en tus estadísticas?

¿Por qué necesitas usar AUTO_DROP en tus estadísticas?

Hoy vamos a hablar de una de las novedades que nos llegaron con SQL Server 2022 pero, de la que se ha hablado poco y no mucha gente conoce. Estoy hablando de la opción AUTO_DROP de las estadísticas. El objetivo de este artículo es explorar esta característica y entender cómo funciona y por qué es beneficioso para ti. 

El reto de las estadísticas manuales

Hubo un tiempo, no tan lejano, donde crear estadísticas manuales era un dolor de cabeza para cualquiera que trabajase con bases de datos SQL Server. Y principalmente era porque las estadísticas manuales eran vinculantes con el esquema. Es decir, si tu o un usuario o aplicación creabais una estadística manual en una tabla, esta estadística iba a bloquear la modificación de la estructura de la tabla. Esto era un problema a la hora de modificar o borrar columnas ya que la sola existencia de la estadística provocaba un error y requería pasos adicionales. Tenías que borrar la o las estadísticas, modificar la tabla y, después, recrear las estadísticas. Por este motivo, con estadísticas manuales, las modificaciones de tabla se convertían en un proceso manual la mayoría de las veces.

Introducción a la opción AUTO_DROP de las estadísticas

Con SQL Server 2022 llegó la opción AUTO_DROP de las estadísticas dispuesta a facilitarnos la vida un poco solucionando, en parte, el problema que mencionaba antes. En resumidas cuentas, cuando habilitamos esta opción para una estadística, esta se crea en un modo que permite que se borre automáticamente cuando se produzca una modificación de la estructura de la tabla.

Características clave de AUTO_DROP

La principal característica de esta funcionalidad es, como hemos visto, que no bloquea la estructura de la tabla. En lugar de eso, la estadística se borra automáticamente cuando es necesario. De esta manera, el comportamiento de las estadísticas manuales se equipara con el de las estadísticas creadas de manera automática por el motor de SQL Server, que también se borran cuando es necesario. 

Además, es importante destacar, que esta es una de esas pocas novedades que SQL Server activa por defecto para todas las bases de datos por lo que si quieres mantener el comportamiento como hasta ahora deberás desactivarlo manualmente.

Buenas prácticas y consideraciones

Ten en cuenta que las estadísticas creadas automáticamente por el motor de base de datos siempre usan la opción AUTO_DROP y no se les puede deshabilitar, si intentas cambiarlo te va a dar error. Esta opción solo está disponible para las estadísticas creadas manualmente y, en bases de datos con nivel de compatibilidad 160, estará activada de manera predeterminada, esto aplica para todas las bases de datos creadas en este nivel de compatibilidad pero también para las que hayas migrado de versiones anteriores. Ten en cuenta este comportamiento y desactívalo si lo deseas. 

 

¿Cómo usar la opción AUTO_DROP?

A la hora de crear una estadística manual podemos definir si queremos o no habilitar la opción AUTO DROP. Por ejemplo, este comando crea una estadística con AUTO_DROP:

Para crear una estadística SIN AUTO_DROP usaremos este:

Si lo que quieres es cambiar la opción AUTO_DROP en una estadística existente puedes hacerlo también. Esta vez con UPDATE STATISTICS. Por ejemplo este sería el script para activar AUTO_DROP en una estadística que no lo tenga.

Para desactivarlo solo cambia el ON del final por un OFF

Para consultar la configuración AUTO_DROP de nuestras estadísticas podemos hacerlo con la vista sys.stats.

Os comparto también un último script para generar automáticamente estos últimos de cambiar la opción AUTO_DROP para todas las estadísticas de usuario.

Conclusión

En conclusión, la opción AUTO_DROP de las estadísticas en SQL Server 2022 representa un avance significativo en la gestión de estadísticas manuales. Su implementación permite reducir la fricción en la modificación del esquema de las tablas, eliminando automáticamente las estadísticas cuando ya no son relevantes. Esto no solo simplifica la administración de la base de datos, sino que también evita errores comunes y la necesidad de procesos manuales adicionales.

Si bien esta funcionalidad está activada por defecto en bases de datos con nivel de compatibilidad 160, es importante conocer su impacto y decidir si se desea mantener o desactivar en cada caso. Al final, la correcta gestión de las estadísticas sigue siendo clave para optimizar el rendimiento de las consultas y garantizar un mantenimiento eficiente de nuestras bases de datos en SQL Server.

Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de Telegram y un canal de YouTube a los que te puede unir. ¡Hasta la próxima! 

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

El extraño error de conversión y su solución

Hoy vamos con un artículo distinto, nos vamos a centrar en un caso práctico que me ha pasado hoy en el trabajo y que me ha parecido lo suficientemente curioso como para traerlo aquí y compartirlo con todos vosotros. La idea es que si alguna vez encontráis este error o alguno parecido sepáis como actuar rápido y sin volveros locos.

Disclaimer 1: Todos los datos que veis son una reproducción de lo que ha pasado, no voy a mostrar ni estructuras ni datos reales.

Disclaimer 2: Vaya por delante que estamos hablando, no solo de un sistema totalmente Legacy, sino de un patio de recreo para todas las malas prácticas habidas y por haber en el mundo de los datos, documentadas y por descubrir. Vosotros me entendéis, ¿verdad? No sé como más decir que esto hay que borrarlo, os prometo que he recurrido a todos los métodos posibles antes de incurrir en ilícito penal. Porque hay veces que la tortura tendría que estar aceptada, ya veréis cuando os cuente…

El misterioso error

Como os digo estaba yo en un día tranquilo, de esos que aprovechas para poner la documentación al día, cuando los lloros de un usuario han perturbado mi paz. Un error salvaje había aparecido:

Hasta aquí todo normal, un error de conversión normal y corriente. “Deja de hacer esa conversión. ¡ Qué estás tratando de convertir a fecha y hora un texto que no es una fecha, ANIMAL !” 

Pero no, iba a ser más complicado, no había ninguna conversión aparente. Era un select * de la tabla sin más. 

El valor de la experiencia

Uno que ya es perro viejo ha ido directamente a mirar la definición de la tabla, ya sabes, más sabe el diablo por viejo que por diablo y, aquí, la experiencia es un grado. No es la primera vez que veo una cosa de estas. Y lo que he visto os sorprenderá.

Bingo. Tenemos una columna calculada que saca el dato de un fichero de texto. Seguro que uno de los campos del JSON tiene un valor en la fecha que realmente no es una fecha o, si lo es, no es de un rango válido. Pero, tenemos en la tabla más de 50 millones de registros, no es plan de ponerse a mirar todos los JSON.

Investigación y solución del error

No hay problema que frene al DBA. Localizar el error es sencillo si sabes como. Basta con usar la misma expresión de CONVERT que tenía la columna calculada pero con TRY_CONVERT que, como expliqué en este vídeo, hace lo mismo que CONVERT pero en caso de error devuelve nulo. Esto en un filtro del where y filtrando por resultados nulos nos va a señalar directamente al culpable, como cuando pillan al malo con restos de pólvora en las manos en nuestra serie favorita de policías americanos.

Como veis un 0 en la fecha era el causante de este expediente X, en mi caso real (no la demo que veis en las imágenes) no era uno sino 258 registros pero vamos, la solución es la misma. UPDATE de esas fechas y a funcionar.

 

Prevención de errores

Una vez arreglado el problema es momento de analizar las causas raíz y ver cómo evitar esto en un futuro. En este caso podríamos resumirlo en hacer las cosas bien pero oye, cuando a uno le están pagando por ello, hay que currarse las respuestas un poco más.

Veamos pues qué pasa:

  1. Usar datos semiestructurados en la base de datos no es una buena idea por rendimiento. Pero es que tampoco tiene validaciones, como hemos podido comprobar. Con una columna de fecha para introducir el dato este error no habría pasado. Directamente este registro incorrecto no se habría escrito en la base de datos.
  2. SQL Server no está preparado para trabajar con JSON, por eso lo del tipo de datos nvarchar(max) en la columna. Mientras que para XML si tenemos un tipo de datos específico para JSON no será hasta SQL Server 2025 que lo veamos (podemos probarlo en preview en Azure SQL Databases y Azure Managed Instance). Este futuro tipo de datos JSON nos permitirá añadir estos controles de los que hablábamos en el punto anterior.
  3. Usar una función CONVERT en una columna calculada es una mala práctica pues, en caso de fallo de los datos, nos devuelve error. Para estos casos, siempre que sea posible es mejor usar TRY_CONVERT. Realmente aquí hay discrepancias de opiniones, y dependerá de vuestro caso. Depende a que deis prioridad, si a tener el resto de datos sin error y el registro incorrecto como nulo o si por el contrario preferís que salte el error para detectarlo y corregirlo.

Conclusión

Los errores de conversión como este pueden ser una pesadilla, pero la realidad es que suelen ser más culpa de un diseño regulero que de un usuario despistado. Aquí la clave es sencilla: si metemos datos como si fueran churros, que no nos sorprenda si luego nos encontramos un «churro» en los resultados. Por otro lado, usar TRY_CONVERT en lugar de CONVERT nos habría ahorrado el susto, pero el problema de fondo sigue siendo el mismo: SQL Server y JSON no son precisamente mejores amigos. 

Aquí estamos, esperando que el tipo de datos JSON nativo llegue en SQL Server 2025. Hasta entonces, toca ser cuidadosos, validar lo que metemos en la base de datos y asumir que, si confiamos ciegamente en los datos, tarde o temprano nos van a dar un disgusto. 

Así que ya sabéis: menos improvisación, más validación y, sobre todo, menos sustos en producción.

Publicado por Roberto Carrancio en Cloud, SQL Server, 1 comentario

Un día en la vida de un DBA: Consultas, Caos y Dudosa Capacidad Técnica (Artículo de HUMOR)

Como DBA de SQL Server, mi día es una mezcla de comedia, drama y suspense, con algunos toques de terror. Aquí os presento cómo transcurrió un día cualquiera en mi vida, repleto de personajes inolvidables, cada uno con su consulta particular y su manera única de contribuir al colapso del servidor. Abrid vuestra mente… y vuestro SQL Server Management Studio.

8:00 AM: Paco “El SELECTor” y la Consulta Infinita

Nada más abrir mi sesión, mi querido compañero Paco, el SELECTor (porque selecciona todo sin filtro), aparece con su ya clásica sonrisa de inocencia. Paco es de los que todavía piensan que SQL Server es algo mágico y que “si no devuelves todas las columnas, seguro te dejas algo importante”.

Me encuentro con su obra maestra, digna de un Anti Premio a la peor consulta del año:

-“¡No entiendo por qué tarda tanto! ¡Si son solo los pedidos del año!”, exclama Paco, mientras agita su taza de café de “No es magia, soy desarrollador”.

Le explico con calma (una vez más) que cada vez que usa SELECT * en producción muere un gatito y que filtrar con funciones como YEAR() no permite que SQL Server use los índices. Con cara de «eso me lo enseñaron en un curso de YouTube», Paco se compromete a mejorar. Mientras tanto, optimizo la consulta:

Optimización: índice + eliminación de funciones

Resultado: Consulta en segundos. Paco asiente con admiración y anota algo en su libreta, probablemente «Pedir más memoria al DBA».

10:00 AM: Toñi “SinWHERE” y el Script Nuclear

Toñi es de esas desarrolladoras que tienen una relación tóxica con el entorno de producción. Tiene un lema: «Si funciona en mi local, funciona en producción». Y hoy, decidió demostrarlo.

Recibo una alerta: «La tabla Employee está vacía». Con el corazón encogido, abro los logs y ahí está el script más temido:

-“¡Uy! Me olvidé el WHERE. Pero no pasa nada, para eso están los backups, ¿no?”, dice Toñi con una sonrisa que desearía no ver nunca más.

Intento explicarle que “no pasa nada” es lo que dice un soldado cuando pisa una mina. Mientras recupero los datos, aplico medidas preventivas:

ATPC. No me fío ni un pelo.

1:00 PM: Manolo “El Shuffle” y el Caos Aleatorio

Manolo, alias “El Shuffle”, es el encargado de los informes y estadísticas. Es fan del “orden aleatorio” porque, según él, “las listas aburren a la gente”. Y claro, cuando quiere algo “aleatorio”, lanza lo siguiente:

-“Es que así queda guay, aleatorio, como mi lista de Spotify”, argumenta Manolo mientras hace girar su silla de oficina cual plato de mesa de mezclas de DJ.

Le explico (otra vez) que NEWID() obliga a SQL Server a generar GUIDs para todas las filas de la tabla y luego ordenarlos. Todas estas operaciones se hacen en memoria y con su maravillosa tabla de 200 millones de registros eso no es una buena idea. Así que le doy una alternativa más eficiente para obtener una muestra aleatoria:

¡Magia! “¿Ves? Es que tú lo complicas todo”, dice con su típica confianza de «yo lo leí en Stack Overflow».

4:00 PM: Javi “El SinÍndices” y la Carga Masiva

Cuando el servidor ya está medio recuperado, aparece Javi, “El SinÍndices”. Su teoría es que los índices ralentizan la carga de datos (y no le falta razón… a medias). Así que en su infinita sabiduría, ha borrado todos los índices de la tabla Invoices para cargar más rápido.

– “¡La carga ha ido como un rayo!”, dice orgulloso.

– “¿Y las consultas que dependen de esa tabla? ¿Les has preguntado cómo van?”, respondo yo con una mirada de fuego.

Para evitar futuros desastres, le tatuo en el antebrazo que si vuelve a borrar índices no respetaré los derechos humanos y recurriré a la tortura en nuestra próxima interacción.

6:00 PM: El Jefe “Sin Backup no Hay Paraíso”

Justo cuando creo que puedo irme, mi jefe aparece con cara de preocupación:

– “¿Tenemos backup de todo esto, verdad?”.

Le muestro mi carpeta de backups, replicados hasta en la luna si es necesario. Porque si algo tengo claro después de años de sufrimiento es que sin backup, no hay DBA que sobreviva.

Conclusión: La vida del DBA es un Reality Show

Un día más salvando a Paco, Toñi, Manolo y Javi de ellos mismos. Un día más optimizando consultas, corrigiendo scripts desastrosos y asegurando que el servidor no arda en llamas. La próxima vez que escuches «¿pero qué hace un DBA?», recuérdales que sin nosotros, el caos reinaría en el mundo de los datos.

Y si alguno de estos personajes te suena… ¡ánimo! Seguro que en tu día a día también te encuentras con alguno de ellos.

Espero que este artículo te haya resultado divertido y ameno. Si tienes alguna duda o comentario, no dudes en contactarnos en Twitter o por mail o dejarnos un mensaje en los comentarios de aquí abajo. Y recuerda que también tenemos un grupo de LinkedIn al que te puedes unir.

Publicado por Roberto Carrancio en Otros, 1 comentario

Always On y el mito de la perdida de datos cero

Cuando hablamos de Alta Disponibilidad en SQL Server, los Grupos de Disponibilidad Always On suelen ser la opción que se menciona con mayor frecuencia. No es para menos, realmente son la solución de alta disponibilidad más completa que ofrece SQL Server. Sin embargo, existe una idea errónea generalizada: que el modo sincrónico de AlwaysOn garantiza la pérdida cero de datos. A primera vista, esta suposición puede parecer razonable, pero en este artículo explicaré por qué no es necesariamente cierto y analizaremos las implicaciones técnicas detrás de esta afirmación.

El mito de la pérdida cero de datos en Always On

El modo sincrónico en los Grupos de Disponibilidad AlwaysOn está diseñado para garantizar que los datos se escriban en todas las réplicas sincrónicas antes de confirmar una transacción. Esto implica que las transacciones no se considerarán completadas hasta que los datos se escriban tanto en la réplica principal como en las secundarias configuradas en modo sincrónico. A simple vista, parece que este comportamiento elimina cualquier posibilidad de pérdida de datos, pero hay ciertos escenarios en los que esto no es así.

Cómo funciona el Always On en modo síncrono

En el modo síncrono, el proceso sigue estos pasos:

  1. El nodo primario recibe una transacción.
  2. Los datos de la transacción se envían a todas las réplicas secundarias configuradas en modo sincrónico.
  3. Las réplicas secundarias confirman que los datos han sido escritos en su registro de transacciones (log).
  4. Solo después de recibir las confirmaciones de todas las réplicas, el nodo primario completa la transacción. Realmente esto se puede ajustar para que no sea necesario esperar a todas las replicas secundarias con la opción REQUIRED_SYNCHRONIZED_SECONDARIES_TO_COMMIT.

Aunque este flujo parece muy robusto, hay ciertas limitaciones y condiciones que pueden comprometer la integridad de los datos.

Excepciones

Todo esto suena muy bonito, precioso diría yo. Y así funciona realmente, excepto cuando algo no va bien. Si la réplica secundaria deja de estar disponible, llámalo reinicio, parcheo o cualquiera de los múltiples otros motivos que puedan surgir dejamos de tener alta disponibilidad. Realmente está contemplado, mirad. Si leemos la documentación nos encontramos con algo que ya no suena tan bien:

En resumidas cuentas, y tiene hasta sentido, si la réplica secundaria no está disponible, por el motivo que sea, las transacciones no se detendrán y seguiremos trabajando con normalidad sobre la réplica principal sin notar nada pero no tendremos alta disponibilidad. Cuando la réplica secundaria nuevamente esté disponible empezará a replicar todas las transacciones pendientes y, un failover, antes de que termine, tendrá pérdida de datos.

Casos prácticos donde puede ocurrir pérdida de datos con Always On

Hemos nombrado ya alguno de los escenarios en los que podríamos tener una pérdida de datos con Always On, pero hay más, estos son los más comunes. 

  • Latencia de red alta: Si la red entre las réplicas tiene una latencia significativa, puede aumentar la probabilidad de inconsistencias. En casos extremos, una réplica secundaria podría quedar rezagada y, como dice la documentación, pasar a modo asíncrono hasta que se recupere la normalidad.
  • Fallos simultáneos en nodos múltiples: En un entorno de clúster, si tanto el nodo primario como las réplicas sincrónicas fallan al mismo tiempo (por ejemplo, por un corte de energía en el data center), se pueden perder datos que no hayan sido escritos en disco.
  • Problemas en el subsistema de almacenamiento: Si el almacenamiento subyacente es compartido para todos los nodos y experimenta corrupción o retrasos significativos, incluso las transacciones confirmadas podrían estar en riesgo.

Prácticas recomendadas en Always On para mitigar riesgos

Si bien sabemos que no es teóricamente imposible la pérdida de datos, también existen una serie de medidas que, como DBAs, podemos tomar para reducir el riesgo. La primera y más eficaz es configurar múltiples réplicas síncronas. Tener más de una réplica puede reducir las probabilidades de pérdida de datos, ya que sería improbable que todas las réplicas fallen simultáneamente. Recuerda que Always On admite un total de 8 réplicas secundarias.

Las siguientes medidas, aunque imprescindibles, no van a tener un impacto tan directo en la reducción del riesgo, simplemente nos permitirán localizar el problema y tomar medidas antes de que sea tarde. Como habrás adivinado ya, estoy hablando de monitorizar la latencia de replicación: Es crucial monitorizar continuamente la latencia entre el nodo primario y las réplicas y tener un buen sistema de alerta para detectar problemas potenciales. También deberemos realizar pruebas regulares de failover: Realizar pruebas regulares ayuda a garantizar que los nodos secundarios estén configurados correctamente y puedan asumir el rol de primario sin perder datos.

Por último, pero no menos importante deberemos tener una solución de respaldo complementaria. Aunque los Grupos de Disponibilidad AlwaysOn son poderosos, una estrategia de copias de seguridad sólida sigue siendo indispensable. No solo para afrontar fallos de la infraestructura, también porque un borrado o actualización incorrecta se replicará inmediatamente por todas las réplicas y las copias de seguridad serán lo único que nos salve.

Conclusión

Los Grupos de Disponibilidad Always On son una solución robusta para alta disponibilidad y recuperación ante desastres. Sin embargo, como hemos visto, el modo sincrónico no es una garantía absoluta de pérdida cero de datos. Comprender estas limitaciones y diseñar una arquitectura con redundancias adicionales es fundamental para minimizar riesgos y garantizar la integridad de los datos. Siempre debemos complementar nuestras configuraciones con monitorización proactiva, pruebas de failover y estrategias de respaldo adecuadas.

Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de Telegram y un canal de YouTube a los que te puede unir. ¡Hasta la próxima!

 

Publicado por Roberto Carrancio en Alta Disponibilidad, SQL Server, 0 comentarios