cloud

Data Saturday Madrid 2026: tres días de formación en SQL Server que no deberías perderte

Hay oportunidades de formación que merece la pena aprovechar, especialmente cuando podemos aprender directamente de profesionales que llevan años trabajando con los problemas que nosotros nos encontramos cada día. Y si además podemos hacerlo presencialmente, compartir experiencias con otros compañeros y acceder a esa formación por un precio casi simbólico, tenemos pocos motivos para dejarla pasar.

Los próximos 5, 6 y 7 de noviembre de 2026, Madrid volverá a convertirse en uno de los principales puntos de encuentro de la comunidad de datos con una nueva edición de Data Saturday Madrid.

Este año tendremos tres días dedicados a la formación, con talleres especializados el jueves y el viernes y una jornada completa de sesiones gratuitas el sábado. Hablaremos de SQL Server, Microsoft Fabric, Power BI, inteligencia artificial y muchas otras tecnologías relacionadas con los datos.

Pero hoy queremos detenernos especialmente en una oportunidad que nos parece extraordinaria: tres talleres de SQL Server, para distintos niveles de experiencia, impartidos por profesionales de referencia internacional.

Y sí, uno de ellos lo impartiré yo.

Formación en SQL Server para todos los niveles en Data Saturday Madrid

Uno de los grandes atractivos de esta edición es que no tendremos que elegir entre formación de iniciación y contenidos avanzados. La organización ha preparado talleres para perfiles muy diferentes, desde quienes todavía no saben escribir una consulta SQL hasta administradores experimentados que quieren profundizar en el funcionamiento interno del motor.

En nuestro caso, tenemos tres propuestas que encajan perfectamente en un itinerario de formación en SQL Server: aprender a consultar datos, comprender y resolver problemas de concurrencia y mejorar el rendimiento de nuestras bases de datos.

Además, los talleres tienen un precio especialmente reducido: 25 € más tasas para las sesiones de dos horas del jueves y 50 € más tasas para las sesiones de cuatro horas del viernes.

Hablamos de formación presencial impartida por especialistas de primer nivel. Por ese importe, resulta complicado encontrar alternativas comparables en el mercado de formación profesional.

T-SQL desde cero: aprende SQL jugando

Viernes 6 de noviembre, de 10:00 a 14:00 | Roberto Carrancio y David Martínez de Frutos | 50 € + tasas

Empezamos por nuestro taller, que impartiré junto a David Martínez de Frutos y que hemos preparado pensando en quienes quieren aprender SQL desde cero, pero también en quienes llevan tiempo trabajando con datos y todavía dependen de otras herramientas para consultarlos.

Actualmente podemos desarrollar aplicaciones, elaborar informes o construir soluciones analíticas sin escribir prácticamente ninguna consulta SQL. Las herramientas cada vez abstraen más operaciones y eso facilita muchísimo nuestro trabajo.

Sin embargo, cuando necesitamos investigar un problema, analizar datos directamente, optimizar una consulta o comprender qué está haciendo realmente una aplicación contra la base de datos, esos conocimientos siguen siendo fundamentales.

Por eso este taller está especialmente orientado a desarrolladores, analistas, arquitectos y científicos de datos. También a administradores de bases de datos junior, o profesionales que quieren comenzar su carrera como DBA.

Porque administrar SQL Server no consiste únicamente en conocer la infraestructura, configurar copias de seguridad o gestionar la alta disponibilidad. Necesitamos entender las consultas que ejecutan nuestras aplicaciones y cómo trabajan con los datos.

Durante las primeras horas aprenderemos mediante ejemplos y ejercicios prácticos. Partiremos de las consultas más sencillas e iremos incorporando filtros, ordenaciones, agrupaciones, combinaciones de tablas y otros recursos de T-SQL que utilizamos habitualmente.

No queremos que los participantes se limiten a ver cómo escribimos consultas. Queremos que las escriban ellos mismos, se equivoquen, corrijan y comprendan por qué funcionan.

Y después del descanso llegará la parte más divertida.

¡Jugaremos al CluedoSQL!

Hemos preparado una actividad de investigación en la que los participantes tendrán que utilizar las consultas SQL para encontrar pistas, relacionar información y resolver un misterio.

De esta manera, terminaremos el taller aplicando todo lo aprendido a un problema práctico, con un objetivo que va mucho más allá de ejecutar consultas siguiendo unas instrucciones.

Nuestra intención es que quienes lleguen sin conocimientos de SQL salgan con una base sólida para continuar aprendiendo y, sobre todo, con confianza para empezar a trabajar directamente con los datos.

Analysing and Resolving Deadlocks, con Erland Sommarskog

Jueves 5 de noviembre, de 17:30 a 19:30 | Erland Sommarskog | Inglés | 25 € + tasas

Subimos considerablemente el nivel con un taller impartido por Erland Sommarskog, uno de los profesionales más reconocidos de la comunidad internacional de SQL Server.

Si llevamos un tiempo administrando bases de datos o desarrollando aplicaciones con cierta concurrencia, seguramente nos habremos encontrado con algún deadlock.

Son situaciones en las que varias sesiones terminan bloqueándose mutuamente y SQL Server necesita intervenir para romper el ciclo, seleccionando una de ellas como víctima.

Hasta aquí, la teoría parece sencilla. El problema aparece cuando tenemos que averiguar qué ha ocurrido realmente y, especialmente, cómo evitar que vuelva a suceder.

Podemos consultar el deadlock XML y visualizar el gráfico correspondiente, pero interpretar correctamente los procesos implicados, los recursos, las operaciones y las condiciones que han provocado el conflicto requiere conocimientos más profundos.

En este taller aprenderemos a obtener las evidencias necesarias para investigar estos problemas y a interpretar la información que proporciona SQL Server.

Es una formación especialmente interesante para DBAs con experiencia, responsables de rendimiento y desarrolladores que trabajan con aplicaciones transaccionales.

Y aprenderlo directamente de Erland Sommarskog es una oportunidad difícil de repetir. Quienes llevamos años trabajando con SQL Server conocemos el valor de sus publicaciones y de sus aportaciones técnicas a la comunidad.

SQL Server Performance Optimization, con Torsten Strauß

Viernes 6 de noviembre, de 15:30 a 19:30 | Torsten Strauß | Inglés | 50 € + tasas

El tercer taller dedicado a SQL Server nos llevará a uno de los terrenos que más nos gustan: el análisis y la optimización del rendimiento.

SQL Server Performance Optimization: From Root Cause Analysis to Sustainable Solutions, impartido por Torsten Strauß, propone un enfoque que consideramos fundamental cuando trabajamos con problemas de rendimiento: dejar de aplicar soluciones por intuición y empezar a investigar con evidencias.

Todos hemos visto situaciones en las que alguien identifica una consulta lenta y propone inmediatamente crear un índice, aumentar la memoria del servidor o modificar alguna configuración.

A veces funciona. Otras veces simplemente trasladamos el problema a otro lugar.

La optimización del rendimiento requiere comprender qué está sucediendo, establecer una referencia inicial, recopilar información y encontrar la causa real del problema antes de decidir qué cambiar.

Durante cuatro horas, Torsten trabajará con escenarios prácticos y reproducibles que permitirán recorrer ese proceso completo: desde la investigación inicial hasta la comparación de posibles soluciones y la medición de los resultados.

Este planteamiento resulta especialmente interesante para DBAs de nivel medio y senior, profesionales especializados en rendimiento y arquitectos que necesitan diagnosticar problemas complejos.

Porque conseguir que una consulta tarde menos después de modificar algo está bien, pero comprender por qué mejora y garantizar que nuestra solución no introduce nuevos problemas es bastante más importante.

¿Cómo convencer a mi jefe para que me deje ir?

Seguramente más de uno habrá llegado hasta aquí pensando que los talleres le interesan, pero que se celebran en horario laboral y tendrá que pedir permiso en su empresa.

Y aquí tenemos un argumento bastante sencillo: la formación no es únicamente un beneficio para el profesional que asiste, también puede aportar un valor directo a la organización.

Una mejor comprensión de T-SQL nos ayuda a desarrollar consultas más eficientes, reducir errores y resolver problemas sin depender constantemente de otros equipos.

Aprender a investigar deadlocks permite identificar problemas de concurrencia que pueden afectar al funcionamiento de aplicaciones críticas. Y mejorar nuestra capacidad de diagnóstico del rendimiento puede evitar intervenciones innecesarias y reducir el tiempo dedicado a resolver incidencias.

Si añadimos que hablamos de talleres especializados de dos o cuatro horas, con precios de 25 € o 50 € más tasas, el coste de la formación resulta muy reducido.

Eso sí, tendremos que contar también con el tiempo de asistencia y, cuando corresponda, los desplazamientos. Pero incluso considerando esos aspectos, la propuesta resulta atractiva.

Para facilitar la gestión, os dejo un correo que podéis adaptar y enviar a vuestro responsable:

Asunto: Solicitud de asistencia a formación técnica en Data Saturday Madrid 2026

Hola, [nombre]:

Los próximos 5 y 6 de noviembre se celebrarán en Madrid varios talleres técnicos organizados dentro de Data Saturday Madrid 2026.

Me gustaría solicitar autorización para asistir al taller [nombre del taller], centrado en [temática], que considero especialmente interesante para mejorar mis conocimientos y aplicarlos directamente a nuestro trabajo.

La formación la impartirán profesionales de reconocido prestigio internacional en tecnologías Microsoft y SQL Server, con un enfoque práctico orientado a resolver problemas reales.

Considero que estos conocimientos pueden aportar beneficios directos a nuestro equipo, especialmente en aspectos como la calidad del código SQL, el diagnóstico de incidencias, la optimización del rendimiento y la reducción de problemas de concurrencia, según el contenido del taller.

Además, la organización ofrece estos talleres a un precio muy reducido:

  • Taller de dos horas: 25 € más tasas.
  • Taller de cuatro horas: 50 € más tasas.

Se trata de una oportunidad de acceder a formación especializada de alto nivel con una inversión económica muy inferior a la habitual en cursos profesionales de estas características.

Por mi parte, me comprometo a compartir con el equipo los conocimientos adquiridos y a identificar posibles mejoras que podamos aplicar en nuestros proyectos.

También aprovecharía la celebración de Data Saturday Madrid el sábado 7 de noviembre, con sesiones técnicas gratuitas sobre SQL Server, Azure, Microsoft Fabric e inteligencia artificial.

¿Podemos valorar mi asistencia a esta formación?

Muchas gracias.

Un saludo.

El correo es solo un punto de partida. Cuanto más consigas relacionar el contenido del taller con las necesidades reales de tu empresa, más fácil resultará justificar la inversión.

Y no olvidemos algo importante: una formación que nos ayuda a evitar una incidencia o a diagnosticar un problema crítico con mayor rapidez puede compensar sobradamente su coste.

El sábado 7 de noviembre tendremos Data Saturday Madrid completamente gratis

Los talleres son solamente el aperitivo. El sábado 7 de noviembre llegará la jornada principal de Data Saturday Madrid 2026, con un programa de sesiones técnicas impartidas por profesionales nacionales e internacionales.

La entrada es gratuita, con una aportación solidaria de 1 € destinada a una ONG, y requiere inscripción previa.

La jornada tendrá lugar en la Escuela Politécnica Superior de la Universidad CEU San Pablo, en el campus de Montepríncipe, Alcorcón, mientras que los talleres del jueves y el viernes se celebrarán en las instalaciones de NTT DATA University, en Madrid.

Durante el sábado podremos asistir a sesiones sobre SQL Server, Azure SQL, Microsoft Fabric, Power BI, inteligencia artificial, ingeniería de datos y otras tecnologías.

Para quienes estamos especialmente interesados en SQL Server, el programa incluye varias propuestas muy interesantes.

Erland Sommarskog hablará sobre expresiones regulares en SQL Server, mientras que Torsten Strauß profundizará en los bloqueos optimizados de SQL Server 2025 y su impacto en la concurrencia.

También tendremos sesiones dedicadas a auditorías de Azure SQL, Azure Database Watcher, búsqueda vectorial y rendimiento, y al papel de las estadísticas en el comportamiento del optimizador de consultas.

Y por mi parte, volveremos a meternos en las tripas del motor con «SQL Server Recovery a fondo: ¿qué ocurre después de un crash?».

Será una sesión de nivel avanzado, especialmente pensada para administradores que quieren comprender qué sucede internamente cuando SQL Server necesita recuperar una base de datos después de una interrupción inesperada.

Hablaremos de recuperación transaccional y de las fases que permiten devolver una base de datos a un estado consistente. Una de esas cuestiones que normalmente damos por resueltas hasta que necesitamos comprender por qué una base de datos tarda tanto en volver a estar disponible.

Pero Data Saturday no es solamente SQL Server. Tendremos sesiones sobre agentes de inteligencia artificial, arquitecturas de datos, DAX, Copilot, desarrollo sobre Fabric y muchas otras materias.

Además, una parte fundamental de estos eventos ocurre fuera de las salas. Poder intercambiar experiencias con otros profesionales, reencontrarnos con compañeros de la comunidad y conversar directamente con los ponentes aporta un valor que difícilmente conseguimos con la formación grabada.

Cómo inscribirse en los talleres y en Data Saturday Madrid 2026

La organización gestiona las inscripciones a los talleres y a la jornada del sábado por separado, así que conviene tenerlo en cuenta al planificar la asistencia.

Los talleres tienen plazas limitadas y podemos inscribirnos en uno o varios, siempre que sus horarios sean compatibles. Se celebrarán en NTT DATA University, Camino de la Fuente de la Mora, 1, Madrid.

Inscripción a los talleres del 5 y 6 de noviembre: https://www.eventbrite.es/e/registro-workshops-pre-data-saturday-madrid-2026-sqlsaturdaymadrid-2003135148151

El sábado se celebrará en el campus de Montepríncipe del CEU San Pablo, en Alcorcón. Aunque la jornada sea gratuita, necesitamos conseguir una entrada. La organización también contempla una asignación de plazas del sábado para inscritos en los talleres, hasta completar el cupo correspondiente.

Inscripción a las sesiones del sábado 7 de noviembre: https://www.eventbrite.es/e/registro-data-saturday-madrid-2026-sqlsaturdaymadrid-2002195163632

Con algo de planificación podemos disfrutar de dos o incluso tres días de formación, combinando un taller especializado con sesiones técnicas y tiempo para compartir experiencias con la comunidad.

Conclusión: una oportunidad para aprender SQL Server de los mejores

No todos los días tenemos en Madrid a referentes internacionales como Erland Sommarskog y Torsten Strauß impartiendo talleres presenciales a precios de comunidad.

Tampoco es habitual poder combinar formación especializada para DBAs experimentados con talleres de iniciación y una jornada completa de conferencias gratuitas.

Si estás empezando con SQL, esperamos verte el viernes por la mañana aprendiendo T-SQL con nosotros y resolviendo el misterio de CluedoSQL. Si ya tienes experiencia, los talleres avanzados y las sesiones del sábado ofrecen una oportunidad excelente para profundizar en aspectos que tienen un impacto directo en nuestro trabajo.

Del 5 al 7 de noviembre tenemos una cita con SQL Server y la comunidad de datos en Madrid. Nos vemos allí.

Publicado por Roberto Carrancio en Cloud, Power BI, Rendimiento, SQL Server, 0 comentarios

SQL Server recovery tras un reinicio: Analysis, Redo y Undo al detalle

Cuando SQL Server arranca después de una caída, arranca una fase de recuperación o recovery. Pero no es que se ponga a “comprobar tablas” como quien revisa una hoja de Excel después de un apagón. Tampoco compara alegremente el MDF con el LDF buscando diferencias a ojo. Lo que hace es mucho más serio, ejecuta un proceso de recuperación basado en el transaction log, en los LSN y en unas reglas bastante estrictas que permiten devolver cada base de datos a un estado transaccionalmente consistente.

Aquí, la explicación habitual de las tres fases (Analysis, Redo y Undo) sirve para una introducción, pero si nos quedamos ahí, estamos mirando solo la superficie e ignorando lo interesante. Y, como suele pasar en SQL Server, lo interesante vive en el barro: páginas, log records, checkpoints, cadenas de transacciones, dirty pages, pageLSN, Compensation Log Records y decisiones internas que explican por qué una base puede tardar segundos en recuperarse o dejarte mirando el error log sin poder hacer nada mientras sientes la presión de tener al jefe mirándote a ti (a todos nos ha pasado).

Antes de entender el recovery hay que entender una verdad incómoda, SQL Server puede tener datos confirmados que aún no están en los data files, y puede tener datos no confirmados que ya están en los data files. Si esta frase te asusta, mejor sigue leyendo.

El modelo real: steal/no-force y por qué necesitamos Redo y Undo

SQL Server usa un modelo de escritura que, simplificando mucho pero sin deformarlo, se puede describir como steal/no-force.

No-force significa que cuando una transacción hace COMMIT, SQL Server no está obligado a escribir inmediatamente en el MDF o NDF todas las páginas de datos modificadas por esa transacción. Lo que debe quedar persistido es el log necesario para garantizar la durabilidad. La página de datos puede seguir en memoria como dirty page y escribirse más tarde, cuando el checkpoint, el lazy writer u otro mecanismo decida que toca sacarla del buffer pool.

Esto, que tanto optimiza el rendimiento, explica también por qué existe el Redo. Si una transacción hizo commit, su log record de commit llegó a disco, pero las páginas modificadas no se habían vaciado todavía al data file, después de una caída SQL Server debe rehacer esos cambios. No porque le apetezca repetir trabajo, sino porque el log promete que esa transacción sobrevivió.

Steal significa que SQL Server puede escribir a disco una página modificada por una transacción que aún no ha hecho commit. Esto ocurre en casos de presión de memoria porque el buffer pool no es infinito. Si una página sucia necesita salir de memoria, puede escribirse al data file siempre que se cumpla la regla de Write-Ahead Logging.

Esto explica por qué existe el Undo. Si una transacción estaba abierta cuando el servidor cayó, algunas de sus modificaciones podrían estar ya en los data files. Como esa transacción no se confirmó, esos cambios deben desaparecer durante la recuperación.

Aquí está la clave que muchos resúmenes no cuentan: Redo existe por no-force; Undo existe por steal. Sin esas dos ideas, Analysis, Redo y Undo son solo tres palabras bonitas que memorizas para presumir en una entrevista técnica.

Write-Ahead Logging: el log manda, el data file obedece

La regla básica de Write-Ahead Logging es sencilla: antes de que una página de datos modificada pueda escribirse en disco, los log records que describen esos cambios deben haberse “endurecido” (persistido, escrito) en el transaction log. Primero el log, luego la página de datos. Siempre. El motor puede retrasar la escritura del data file, pero no puede escribir una página con cambios cuyo log correspondiente no haya llegado antes a disco.

Esto permite que el recovery sea posible. Si después de una caída SQL Server encuentra una página en disco que no tiene ciertos cambios, puede rehacerlos desde el log. Si encuentra una página en disco que sí tiene cambios de una transacción incompleta (no está el commit en el log), puede deshacerlos también usando el log. El LDF no es un fichero auxiliar sin importancia; es la fuente de verdad de la recuperación. El MDF contiene páginas. El LDF contiene la historia necesaria para saber qué hacer con ellas.

Log Sequence Number: Un identificado con mucho significado

Cada log record tiene un LSN (Log Sequence Number) que identifica una posición lógica dentro del transaction log. En herramientas internas o no documentadas podemos verlo en formato de tres partes, algo parecido a 0000002a:000001f0:0001. No hace falta enamorarse del formato, pero sí entender el concepto, los LSN ordenan la historia de la base de datos.

Las páginas de datos también guardan un LSN en su cabecera, conocido como pageLSN. Ese valor indica el último log record aplicado a esa página. Durante Redo, esta comparación es fundamental. Si el log record tiene un LSN mayor que el pageLSN, probablemente esa modificación no está en la página y debe aplicarse. Si el pageLSN ya es igual o superior, SQL Server puede saltarse ese redo para esa página.

Ese detalle es clave para la velocidad de recuperación. Redo no es “aplicar todo a lo bruto”. Redo es idempotente. Puede recorrer log records y decidir si una página necesita o no necesita esa operación. Esto evita aplicar dos veces el mismo cambio y convertir la recuperación en una trituradora de consistencia. SQL Server no repite una modificación porque sí, compara la historia del log con la historia que la página dice tener.

¿Qué hay realmente en el transaction log?

El transaction log no guarda simplemente “sentencias SQL”. No esperes encontrar un bonito UPDATE Clientes SET Saldo = Saldo – 100 WHERE Id = 7 esperando a que alguien lo vuelva a ejecutar. Sería mucho más sencillo de entender pero no, el log trabaja a un nivel mucho más físico y orientado al motor (por suerte).

Podemos encontrar registros de inicio de transacción, commit, abort, modificaciones de filas, cambios en páginas, asignaciones de extents, cambios en estructuras de índices, formateos de páginas, operaciones sobre metadatos y muchas otras entradas. Algunas son más físicas, otras más lógicas, y algunas dependen del tipo de operación. El motor registra lo que necesita para poder rehacer y deshacer de forma fiable que al final es el objetivo, no que nosotros lo entendamos de un vistazo.

Una transacción no aparece como un bloque bonito y compacto. Sus log records se enlazan mediante una cadena. Cada log record de una transacción conoce, de una forma u otra, cuál fue el log record anterior de esa misma transacción. Ese enlace permite recorrer hacia atrás la actividad de una transacción cuando toca hacer Undo.

Esto es importante porque Undo no recorre el log entero hacia atrás como quien rebobina una cinta VHS desde el final hasta el principio (si entendiste la referencia una pregunta, ¿tu también haces ruiditos al levantarte?). Undo trabaja sobre las transacciones perdedoras, las que estaban activas en el momento de la caída, y sigue sus cadenas de log records para deshacer lo que haya que deshacer.

Si alguna vez has mirado fn_dblog (esa función no documentada que todos decimos usar “solo en laboratorio”), habrás visto columnas como Current LSN, Previous LSN, Transaction ID, Operation, Context, Page ID, Begin Time, End Time o Transaction Name. No es una interfaz soportada para montar procesos, pero ayuda mucho a entender que el log es una secuencia estructurada de hechos, no una transcripción amable del SQL original.

Consultando el log

Un ejemplo típico de investigación en laboratorio sería algo así:

No recomiendo basar una operativa de producción en esto. Pero como herramienta para aprender cómo respira el motor, tiene mucho valor.

Checkpoint: no es un backup ni un commit

El checkpoint es otro concepto maltratado con una constancia admirable. Y vamos a dejarlo claro desde el principio. Un checkpoint no confirma transacciones. No garantiza que “todo esté guardado”. No sustituye a un backup. Un checkpoint ayuda a reducir el trabajo que tendría que hacer SQL Server durante recovery escribiendo dirty pages al disco y registrando en el log un punto útil desde el que iniciar el análisis.

Cuando se ejecuta un checkpoint, SQL Server intenta vaciar a disco páginas sucias de la memora. También escribe registros de checkpoint en el log. En un modelo tipo ARIES (Algorithms for Recovery and Isolation Exploiting Semantics), el checkpoint es “fuzzy”, porque no detiene el mundo mientras todo queda perfectamente quieto. La actividad puede continuar mientras el checkpoint se produce. Esto implica que el checkpoint no representa una fotografía congelada e impecable de la base de datos, sino un punto de referencia operativo.

Aquí conviene ser muy preciso. Decir “SQL Server empieza desde el último checkpoint” es una simplificación aceptable para una conversación de pasillo, pero incompleta. La fase de Analysis usa la información del checkpoint para reconstruir qué transacciones estaban activas y qué páginas estaban sucias. A partir de ahí, Redo puede necesitar empezar en el recLSN más antiguo de las páginas sucias, que puede ser anterior al propio final del checkpoint.

Por eso no basta con pensar “checkpoint reciente igual recovery trivial”. Ayuda, claro. Pero si hay páginas sucias antiguas, transacciones largas o mucho log activo, el recovery puede tener más trabajo del que uno esperaría mirando solo la hora del último checkpoint.

Tipos de checkpoints

También hay que distinguir entre checkpoints automáticos, manuales e indirect checkpoints. Hablamos de este tema aquí. Con indirect checkpoints, SQL Server intenta cumplir un objetivo de tiempo de recuperación por base de datos, marcado mediante TARGET_RECOVERY_TIME. Esto no significa que garantice por contrato divino que toda recuperación durará ese tiempo. Significa que el motor regula la escritura de páginas sucias para intentar limitar el volumen de Redo. Undo es otra historia, especialmente si hay transacciones largas. Sí, se puede configurar un objetivo precioso y luego estrellarse contra una transacción monstruosa. SQL Server no negocia con tu optimismo.

Arranca la instancia: ¿qué ocurre antes de que veas la base ONLINE?

Ahora si, llegamos a lo que estabas esperando, cuando el servicio de SQL Server arranca después de una caída, primero debe recuperar lo necesario para que la instancia pueda funcionar. Las bases de sistema tienen prioridad obvia. La base de datos master debe estar operativa la primera porque ahí vive la información fundamental de la instancia. Por su lado, model importa porque se usa para crear tempdb. Y tempdb, por su parte, no se recupera como una base normal, se recrea en cada arranque. Nadie debería diseñar nada persistente en tempdb, aunque hay quien lo ha intentado, estos ojos lo han visto.

Después llega la recuperación de las bases de usuario. Cada base tiene su propio transaction log, su propio estado y su propio proceso de recovery. Una base puede estar en RECOVERING, otra ya en ONLINE, otra en RECOVERY_PENDING porque no puede acceder a algún fichero, y otra en SUSPECT si la recuperación falla por problemas graves. No hay una única película para toda la instancia, hay una película por base de datos.

Podemos ver estados básicos desde master:

Durante una recuperación en curso, también puede aparecer actividad en sys.dm_exec_requests con comandos relacionados con startup o recovery. Pero ojo, no siempre veremos lo que queremos, no siempre durante mucho tiempo, y no siempre con el detalle que nos gustaría. Bienvenido al mundo real, donde los DMVs ayudan pero no hacen milagros.

El error log sigue siendo una fuente básica. SQL Server suele informar del progreso de recovery con mensajes en el error log del tipo “Recovery of database X is Y% complete”. Cuando una base tarda, el error log es el primer sitio donde mirar antes de convocar una reunión de emergencia con gente opinando fuerte.

No es elegante, pero es útil. Y en operaciones, lo útil gana a lo elegante casi siempre.

Analysis: reconstruir el campo de batalla

Pero, volvamos un momento al principio. Ya sabemos todo lo que pasa, pero esto no sale de la nada. Hay una primera fase en la que SQL Server analiza la situación y decide que es lo que necesita para recuperarse. Esta fase se conoce como Analysis. 

En la fase de Analysis el motor recorre el transaction log desde el último checkpoint relevante y reconstruye dos estructuras internas fundamentales: la Dirty Page Table y la Active Transaction Table.

La Dirty Page Table, o DPT, contiene páginas que estaban sucias en el momento del fallo. Para cada página interesa especialmente el recLSN, es decir, el LSN más antiguo que pudo haber ensuciado esa página y que todavía puede requerir Redo. Ese recLSN marca desde dónde podría ser necesario rehacer cambios para esa página.

La Active Transaction Table, o ATT, contiene las transacciones que estaban activas cuando se produjo la caída. Esas transacciones son candidatas a Undo, salvo que durante el análisis se encuentre que terminaron correctamente. En terminología clásica, las transacciones confirmadas antes del fallo son winners. Las transacciones activas en el momento del fallo son losers. El motor no se emociona con los nombres, pero la clasificación es bastante clara: unas sobreviven y otras se deshacen.

Subfases de Analysis

Durante Analysis, SQL Server procesa log records para entender qué transacciones empezaron, cuáles hicieron commit, cuáles hicieron rollback, qué páginas se modificaron y qué trabajo quedó en el aire. No está aplicando cambios a páginas todavía. Está reconstruyendo la situación exacta necesaria para decidir después.

Esta fase también identifica el punto desde el que Redo debe empezar. Y este matiz es importante, Redo no tiene por qué empezar exactamente en el último registro de checkpoint. Empieza en el menor recLSN de la DPT. Si una página lleva sucia desde antes del checkpoint y todavía no ha llegado a disco en el estado esperado, el log necesario puede arrancar antes de lo que una explicación superficial sugeriría.

Aquí se ve por qué el tamaño del log activo y la antigüedad de ciertas páginas sucias importan. No hablamos solo de “cuánto pesa el LDF”. Un LDF enorme lleno de VLFs inactivos puede ser menos problemático que una porción activa relativamente menor pero llena de historia que el motor necesita analizar. El tamaño impresiona, el LSN manda.

Para inspeccionar información útil del log moderno, tenemos sys.dm_db_log_stats, que aporta una foto bastante mejor que jugar a adivinar mirando el tamaño del fichero:

No hace falta obsesionarse con cada columna, pero sí entender la idea, el motor no recupera “un fichero”. Recupera una secuencia de log records relevante para llevar la base a consistencia. Los VLFs, el log activo y los LSN son parte de esa conversación.

Dirty Page Table: no todas las páginas sucias son iguales

Una dirty page es una página modificada en memoria que aún no se ha escrito al data file con esos cambios. Esa definición parece simple, pero tenerlo claor evita muchos disparates.

Una dirty page puede contener cambios de una transacción ya confirmada. Si cae el servidor antes de que esa página llegue a disco, Redo deberá rehacer esos cambios desde el log.

También una dirty page puede contener cambios de una transacción no confirmada. Si esa página llegó a disco antes del fallo, Undo tendrá que deshacer esos cambios. Si no llegó a disco, quizá Redo los repita primero para reconstruir la historia y luego Undo los quite. Esto suena raro la primera vez: ¿para qué rehacer algo que luego vas a deshacer? Porque el algoritmo de recuperación repite la historia hasta el punto del fallo y después revierte los losers. Es más robusto y permite que el proceso sea reiniciable.

Una dirty page puede contener mezcla de cambios de varias transacciones, unas confirmadas y otras no. SQL Server no puede tratar la página como una unidad transaccional simple. Tiene que mirar log records, LSNs, estado de transacciones y aplicar o deshacer lo que corresponda. Quien diga “esa página estaba sucia, luego era dato sin commit” está mezclando churras con merinas.

La DPT no existe para decorar la fase de Analysis. Existe para que Redo no tenga que revisar el universo entero. Si una página no estaba sucia en el momento crítico o su pageLSN demuestra que ya contiene un cambio, el motor puede evitar trabajo innecesario. El recovery eficiente depende mucho de no rehacer tonterías. Ya tenemos bastante con las que ejecutan algunas aplicaciones.

Active Transaction Table: el censo de los culpables

La ATT contiene transacciones activas durante la recuperación. Para cada transacción interesa saber su estado, su último LSN y la cadena de log records que permitirá deshacerla si hace falta.

Durante Analysis, una transacción puede aparecer como activa porque se ve un LOP_BEGIN_XACT. Si después aparece un commit endurecido en el log, esa transacción pasa a ser winner. Si no aparece commit ni abort completo antes del final del log disponible, será loser y entrará en Undo.

Aquí hay un detalle crítico, una transacción solo se considera confirmada de verdad si el commit está en el log persistido. Si la aplicación envió un COMMIT, pero el servidor cayó antes de endurecer el log y antes de confirmar al cliente, esa transacción no existe como confirmada a efectos de recovery. Si el cliente recibió confirmación de commit, SQL Server ya debía haber endurecido el log necesario. Esa es la promesa de durabilidad, la D de ACID.

Esto también explica algunas confusiones después de una caída. El usuario jura que “le dio a guardar”. La aplicación quizá envió algo. La red quizá hizo de las suyas. El servidor quizá no llegó a confirmar. Recovery no trabaja con sensaciones del usuario. Trabaja con log records persistidos. Bastante hace.

Redo: repetir la historia, incluso la parte que luego se va a borrar

Acabamos de explicar la fase de Redo cómo “reaplicar transacciones confirmadas”. Es una forma cómoda de contarlo, pero a bajo nivel es imprecisa. En un enfoque tipo ARIES, Redo repite la historia desde el punto necesario. Eso puede incluir cambios de transacciones que estaban sin confirmar en el momento del fallo. Luego Undo se encargará de revertirlas.

¿Por qué hacer eso? Porque así el motor reconstruye el estado de las páginas tal y como podía estar en el momento del crash, basándose en el log. Después aplica la lógica transaccional para eliminar los efectos de las transacciones perdedoras. Este enfoque es idempotente, es decir facilita que la recuperación sea correcta aunque se interrumpa y tenga que reiniciarse. Sí, recovery también puede fallar a medias si se vuelve a caer el servidor. El diseño contempla ese escenario porque, sorprendentemente, el hardware no lee nuestras ventanas de mantenimiento.

Durante Redo, SQL Server recorre log records desde el redoLSN determinado por Analysis. Para cada registro relevante sobre una página, puede comprobar si esa operación ya está reflejada en la página usando el pageLSN.

La lógica conceptual sería algo así:

Si logRecord.LSN > page.pageLSN:

  • aplicar operación de redo
  • actualizar page.pageLSN

Si logRecord.LSN <= page.pageLSN:

  • saltar, la página ya contiene ese cambio o uno posterior

Evidentemente, el motor real es bastante más complejo, porque no todos los log records son simples modificaciones de una fila en una página de datos. Hay allocation pages, metadatos, estructuras de índices, operaciones mínimamente registradas bajo ciertas condiciones, cambios en páginas IAM, PFS, GAM, SGAM, etc. Pero la idea del pageLSN como mecanismo de idempotencia es fundamental.

Undo: caminar hacia atrás por las transacciones loser

Undo trabaja sobre las transacciones que Analysis dejó marcadas como incompletas. Para cada una, SQL Server recorre su cadena de log records hacia atrás usando los enlaces internos del log. No deshace “sentencias”, deshace efectos registrados.

Si una transacción modificó diez páginas, generó muchos log records y quedó abierta, Undo tendrá que revertir lo necesario en orden inverso. Esto puede implicar borrar filas insertadas, restaurar valores anteriores, revertir asignaciones o deshacer cambios de estructuras internas. Depende de lo que la transacción hubiera hecho.

Aquí entran los Compensation Log Records, o CLRs (si otra vez Microsoft reutilizando las mismas siglas en el mismo producto para dos cosas distintas). Cuando SQL Server deshace una operación, registra en el log que ha hecho esa compensación. Esto puede sonar extraño: “¿también loguea el rollback?”. Sí. Y menos mal.

Los CLRs hacen que Undo sea reiniciable. Si el servidor vuelve a caerse en mitad del Undo, al arrancar de nuevo SQL Server no empieza desde cero como si nada hubiera pasado. Puede ver los CLRs y saber qué partes del undo ya se realizaron. Además, los CLRs suelen incluir información como el siguiente LSN que debe deshacerse, el famoso undoNextLSN en la literatura de ARIES.

Un CLR se rehace durante Redo si hace falta, pero no se deshace otra vez durante Undo. Esto evita bucles absurdos donde el motor deshace el deshacer del deshacer hasta que alguien apaga el CPD para que deje de sufrir.

Esta parte explica por qué matar una sesión que está haciendo rollback no te salva. Si una transacción enorme empieza a revertir, SQL Server tiene que dejar la base consistente. Puedes reiniciar la instancia con la esperanza de “cancelar el rollback”, claro. Pero no servirá de nada, al volver, SQL Server retomará el trabajo necesario, porque el log conserva la obligación de terminarlo.

Ejemplos concretos

Pongamos un caso simple. Una transacción inserta una fila y hace commit. El log de la inserción y el commit están en disco. La página de datos modificada sigue en memoria. Cae el servidor. Al arrancar, Analysis detecta lo necesario, Redo encuentra el log record de la inserción, lee la página desde el data file, comprueba que su pageLSN es anterior y reaplica la inserción. La transacción era winner, así que Undo no la toca. Resultado: la fila queda.

Ahora otro caso. Una transacción inserta una fila, no hace commit, pero la página se escribe al data file antes de la caída. Al arrancar, Redo puede no necesitar aplicar esa inserción porque la página ya la contiene. Pero Analysis detecta que la transacción era loser. Undo seguirá la cadena de log records de esa transacción y eliminará la fila. Resultado: la fila desaparece.

Tercer caso. Una transacción inserta una fila, no hace commit y la página no llegó a disco. Redo puede reaplicar la inserción para repetir la historia. Luego Undo la elimina. Resultado final: la fila desaparece igualmente. Parece absurdo hasta que entiendes que el algoritmo prioriza una recuperación reiniciable y consistente sobre una intuición humana de “no hagas lo que vas a deshacer”. El motor no está intentando parecer eficiente en una pizarra, está intentando ser correcto en escenarios complejos.

Otro ejemplo práctico

Vamos a ver un ejemplo completo de manera práctica. Imaginemos una base con una tabla Ventas. Tenemos estas operaciones:

Mientras esa transacción sigue abierta, SQL Server genera log records para el inicio de transacción, el update y el insert. Las páginas afectadas quedan sucias en memoria. Puede ocurrir que una de esas páginas se escriba a disco antes del commit. No pasa nada mientras el log correspondiente se haya escrito antes, cumpliendo WAL.

Ahora cae el servidor.

Durante Analysis, SQL Server ve que esa transacción empezó, ve sus operaciones y no encuentra commit. La mete en la lista de losers.

Durante Redo, SQL Server revisa los log records relevantes. Si la página del update no tiene aplicado el cambio según su pageLSN, lo rehace. Si la página del insert ya lo tenía, lo salta. Al final de Redo, la base puede parecerse al estado físico del momento de la caída, incluyendo cambios no confirmados.

Durante Undo, SQL Server recorre hacia atrás la transacción loser. Primero deshace el insert de VentaId = 99. Después revierte el update de VentaId = 10. Por cada compensación escribe CLRs. Al terminar, la transacción queda abortada de forma consistente.

Resultado: ningún cambio de esa transacción queda visible como confirmado. Pero durante el camino, el motor quizá rehizo cosas que luego deshizo. Y eso está bien. El objetivo no es que el algoritmo coincida con nuestra intuición simplificada; el objetivo es que sobreviva al desastre.

¿Por qué una base puede tardar tanto en recuperarse?

Cuando una base tarda en recovery, normalmente hay que pensar en tres grandes fases de trabajo: Analysis, Redo y Undo. Cada una puede doler por motivos distintos.

Analysis puede tardar si hay mucho log que recorrer, muchos VLFs activos, un checkpoint lejano o una estructura de log poco saludable. No es solo tamaño en GB. Un log con miles y miles de VLFs puede penalizar operaciones de gestión y recuperación. Desde hace años SQL Server ha mejorado la creación de VLFs, pero los entornos viejos, los crecimientos pequeños y frecuentes, y los logs mal dimensionados siguen existiendo. La mala configuración nunca muere, solo cambia de servidor.

Redo puede tardar si hay muchas modificaciones confirmadas pendientes de llegar a los data files, si el almacenamiento es lento, si hay mucha actividad que reconstruir o si el motor debe leer y escribir muchas páginas. Aquí el cuello puede estar en I/O, CPU, lectura de log, escritura de data files o una combinación antipática.

Undo, por su parte, puede tardar si había transacciones largas abiertas. Esta es una de las causas más desesperantes porque muchas veces el negocio solo ve “SQL Server no arranca la base de datos” cuando en realidad el motor está pagando la factura de una transacción enorme que alguien dejó a medias. Procesos batch gigantes, cargas masivas dentro de una única transacción, mantenimientos mal planteados, despliegues que mezclan DDL y DML como si no hubiera mañana… Luego recovery tarda y la culpa, por supuesto, es de la base de datos. Faltaría más.

Un punto fundamental, los checkpoints ayudan sobre todo a limitar Redo, pero no eliminan Undo. Si tienes una transacción abierta durante tres horas y cae el servidor, el checkpoint puede haber ido escribiendo páginas, pero esa transacción sigue siendo loser si no hizo commit. Y si sus cambios llegaron al data file, habrá que deshacerlos. El checkpoint no convierte una transacción abierta en confirmada. Esto parece obvio hasta que uno escucha ciertas teorías en producción.

Recovery y paralelismo: el log es secuencial, aunque SQL Server intente hacer trampas

Hay una pregunta muy natural cuando vemos una base recuperando durante minutos: “¿por qué no usa todos los cores y acaba antes?”. La respuesta corta es que no todo en recovery se puede paralelizar, porque el transaction log es una secuencia ordenada de hechos. Y cuando tu fuente de verdad es una cadena de LSNs, no puedes partirla alegremente como si fuera una tabla heap en un SELECT COUNT(*). 

La fase de Analysis es, por naturaleza, muy secuencial. SQL Server tiene que recorrer el log hacia delante desde el punto de partida relevante, interpretar los log records en orden, reconstruir la Dirty Page Table y la Active Transaction Table, detectar transacciones que empiezan, transacciones que terminan, páginas que se ensucian y estados que cambian. El orden importa. Si lees primero el COMMIT y luego el BEGIN TRAN no serviría de nada..

Por eso Analysis no suele ser una fase que escale felizmente con más CPU. Puede estar limitada por lectura del log, por número de VLFs, por latencia de almacenamiento, por CPU interpretando registros o por una mezcla de todo. Pero no esperes ver dieciséis cores al cien por cien solo porque la instancia tenga muchos procesadores disponibles.

Paralelismo en Redo

En Redo la cosa es más interesante. Conceptualmente, Redo también recorre el log hacia delante desde el redoLSN, pero aplicar los cambios sobre páginas puede paralelizarse en ciertos escenarios. SQL Server puede tener un worker coordinador que lee, clasifica o distribuye trabajo, y workers de redo que aplican cambios sobre páginas diferentes. Ahora bien, esto no significa paralelismo infinito. Hay dependencias, hay latches, hay páginas calientes, hay orden lógico que respetar y hay I/O real contra data files..

En SQL Server 2016 se introdujo parallel redo para bases de datos secundarias en Availability Groups. Microsoft documenta que incluso en versiones con arquitectura de parallel recovery puede haber redo single-threaded, y que en determinados escenarios la instancia asigna threads de redo hasta ciertos límites. En SQL Server 2019 y anteriores había un límite de 100 threads disponibles para redo en ese contexto, y si se agotaban, algunas bases de datos podían quedarse con un único thread de redo.

SQL Server 2022 mejora este punto con ParallelRedoThreadPool, un pool de threads a nivel de instancia compartido por bases de datos con trabajo de redo. La documentación de Microsoft indica explícitamente que este pool permite que varios threads procesen log records de una misma base en paralelo, y que ParallelRedoBatchRedo mejora recovery, catch-up redo y crash recovery redo. 

Pero cuidado, parallel redo no convierte el transaction log en una estructura desordenada. El log sigue siendo secuencial. Lo que se paraleliza es parte del trabajo de aplicación de cambios sobre páginas, no la semántica completa de la recuperación. Hay un orden que interpretar y dependencias que respetar. Por eso el escalado no es lineal. Pasar de uno a ocho workers no significa dividir el tiempo entre ocho, igual que subir MAXDOP no convierte una consulta mal escrita en un formula uno, solo hace que moleste en más schedulers.

Paralelismo en Undo

En Undo, el paralelismo también tiene límites claros. Undo no consiste en leer el log hacia atrás de forma global y repartirlo como cromos. SQL Server deshace transacciones loser siguiendo sus cadenas de log records, normalmente desde el último LSN de cada transacción hacia atrás. Esa cadena tiene orden. Para una transacción concreta, el motor no puede deshacer alegremente el paso 3 antes de entender el paso 4 si hay dependencias entre operaciones. Puede haber trabajo concurrente cuando existen varias transacciones que deshacer, pero una transacción monstruosa sigue siendo una transacción monstruosa. Y si esa transacción concentra el trabajo, el paralelismo tiene poco donde rascar.

VLFs: el log físico también importa

El transaction log está dividido internamente en Virtual Log Files, VLFs. SQL Server escribe log de forma secuencial, pero gestiona reutilización y truncado a nivel de VLFs. Un número razonable de VLFs facilita la vida. Un número disparatado puede empeorar tiempos de arranque, recovery, backups de log, restores y otras operaciones.

El problema clásico aparece cuando un log crece muchas veces en incrementos pequeños. Cada crecimiento crea VLFs nuevos. Repite eso durante años y terminas con una colección absurda de VLFs. Luego llega un failover o un reinicio y todo el mundo descubre que el LDF tenía historia clínica.

Podemos revisar los VLFs con:

No se trata de perseguir un número mágico universal. Se trata de evitar extremos ridículos. Un log de cientos de GB con pocos VLFs enormes puede no ser ideal. Un log con decenas de miles de VLFs pequeños suele ser una invitación al sufrimiento. La solución pasa por dimensionar bien el log, configurar crecimientos sensatos y, si ya está destrozado, planificar una corrección reduciendo y creciendo de forma controlada. Y no, hacer un shrink diario no cuenta como mantenimiento. 

Para profundizar sobre este tema pasate por este otro artículo que escribí hace un tiempo.

El papel del pageLSN: la pieza que evita el caos

Cada página de datos de SQL Server tiene una cabecera con metadatos. Entre ellos está el LSN de la última modificación aplicada. Ese pageLSN permite al motor saber hasta qué punto del log está actualizada esa página.

Durante Redo, SQL Server no puede limitarse a aplicar cada log record ciegamente. Imagina que una página ya fue escrita al data file antes de la caída con ciertos cambios. Si Redo aplicase otra vez esos cambios sin comprobar nada, duplicaría inserciones, repetiría modificaciones o rompería estructuras internas. 

La comparación entre el LSN del log record y el pageLSN permite decidir. Si la página ya refleja ese cambio o uno posterior, se salta. Si no, se aplica. Esta propiedad hace que Redo sea seguro aunque el estado físico de las páginas sea heterogéneo: unas estaban actualizadas en disco, otras no, unas contenían cambios confirmados, otras cambios sin confirmar.

Podemos inspeccionar páginas con DBCC PAGE, aunque esto es un procedimiento no documentado y entra ya en terreno de laboratorio y con cuidado. Primero habría que activar la salida al cliente:

El tercer parámetro sería el número de página. No es algo para ejecutar alegremente en producción porque “lo he visto en un blog”. Pero entender que las páginas tienen cabecera, identificadores, tipo, m_lsn y metadatos internos ayuda a dejar de ver la base como un conjunto abstracto de tablas flotando en el éter.

Allocation pages también participan en la fiesta

Cuando hablamos de recovery solemos pensar en filas de usuario, pero SQL Server también debe recuperar estructuras de asignación. Páginas PFS, GAM, SGAM, IAM y otras páginas internas registran qué extents y páginas están asignados, qué páginas tienen espacio libre, qué objetos poseen qué estructuras y demás información imprescindible. Hablamos de estas páginas de asignación en este artículo.

Si una transacción asignó páginas para insertar datos y luego cayó sin commit, Undo puede tener que revertir no solo la fila, sino también efectos de asignación si procede. Si una operación confirmó asignaciones pero las páginas de asignación no llegaron a disco, Redo tendrá que reconstruirlas. El recovery no consiste en “poner filas bien”; consiste en dejar toda la base internamente coherente.

Esto importa especialmente en operaciones intensivas como cargas masivas, creación o reconstrucción de índices, operaciones mínimamente registradas, cambios de esquema, particionado, movimientos de datos. Muchas de estas operaciones generan patrones de log distintos a un update trivial de una fila. Si una caída ocurre en mitad de una operación de mantenimiento grande, el recovery puede tener bastante trabajo aunque el usuario solo vea “estaba reconstruyendo un índice”.

Aún hay quien lanza mantenimientos masivos en una única ventana sin mirar log, VLFs, almacenamiento, HA ni plan de vuelta atrás. Luego aparece una recuperación larga y se sorprende. La sorpresa es humana, la causa, bastante menos misteriosa.

Minimally logged no significa “sin log”

Otro malentendido habitual, las operaciones mínimamente registradas. En ciertos modos de recuperación y bajo ciertas condiciones, SQL Server puede registrar menos información para operaciones como bulk load, SELECT INTO, creación de índices o algunas operaciones masivas. Pero “mínimamente registrado” no significa “no registrado”.

El motor sigue necesitando información suficiente para garantizar consistencia y recuperación. Puede no registrar cada fila del mismo modo que en una operación completamente registrada, pero registra asignaciones, cambios estructurales y lo necesario para rehacer o revertir según corresponda. Si no pudiera recuperarlo, no sería una optimización, sería una ruleta rusa.

Esto tiene impacto en backups, restores y recovery. En modo bulk-logged, por ejemplo, ciertas operaciones pueden afectar a la recuperación a un punto en el tiempo si hay operaciones mínimamente registradas implicadas. No es el foco de este artículo, pero conviene recordar que el log siempre está en la conversación..

Fast Recovery: online no siempre significa recovery terminado

En determinados escenarios y solo en SQL Server Enterprise, SQL Server puede poner una base online después de completar Redo, mientras Undo continúa en segundo plano. Esto se conoce como Fast Recovery. La idea es permitir acceso a la base antes de que termine todo el rollback de transacciones loser.

Pero cuidado con la interpretación. Que la base esté online no significa que todo sea gratis. Las transacciones que siguen en Undo pueden mantener bloqueos o generar comportamientos que hay que entender. El motor protege la consistencia, no concede un pase VIP a datos que deberían deshacerse.

Este matiz es importante porque a veces se interpreta ONLINE como “ya está todo hecho”. En recovery, ONLINE puede significar “ya puedes entrar, pero todavía estoy limpiando el desastre de alguien”. Es como abrir la tienda mientras aún están fregando el pasillo donde alguien tiró aceite. Puedes pasar, pero no conviene hacer piruetas.

ADR: cuando Undo deja de ser el monstruo de siempre

Accelerated Database Recovery cambia bastante la conversación. Introducido en SQL Server 2019, ADR rediseña la forma en la que SQL Server aborda rollback y recovery para que el tiempo de recuperación sea mucho menos dependiente del tamaño de la transacción más larga.

ADR se apoya en varios componentes, pero los dos que más nos interesan aquí son el Persistent Version Store y el logical revert. En lugar de depender exclusivamente de deshacer físicamente una cadena enorme de cambios durante Undo, ADR mantiene versiones persistentes dentro de la propia base de datos. Esto permite que el rollback lógico sea mucho más rápido desde el punto de vista de disponibilidad.

Con ADR, una transacción larga cancelada puede revertirse de forma casi instantánea para el usuario, aunque internamente luego haya limpieza asíncrona. Durante recovery, el impacto de transacciones largas se reduce de manera drástica. Esto no elimina Analysis ni Redo, pero cambia el peso relativo de Undo y evita muchas situaciones históricas donde una transacción monstruosa dejaba la base atrapada durante demasiado tiempo.

ADR también introduce el concepto de sLog, un stream secundario usado para acelerar la recuperación registrando operaciones no versionadas necesarias. Durante recovery, SQL Server puede usar ese sLog para rehacer la parte crítica con menos coste. No es simplemente “Undo más rápido”; es un cambio de arquitectura en cómo se conserva y usa la información necesaria para volver a consistencia.

Eso sí, ADR no es magia gratuita. Almacenar versiones persistentes tiene coste. Hay limpieza del PVS. Hay efectos sobre espacio, log y determinadas cargas de trabajo. Como casi todo en SQL Server, arregla problemas reales a cambio de introducir cosas que hay que monitorizar.

Activar ADR

Lo primero sería saber si tenemos ADR habilitado o no. Esto en Azure SQL es fácil, viene activado y no lo podemos quitar así que lo tendremos activado. En SQL Server On-Premise podemos ver si ADR está activado en una base con:

Podemos activarlo (cuando proceda y después de probarlo como adultos responsables) con:

No lo conviertas en una receta universal sin medir. En Azure SQL Database, ADR forma parte del comportamiento moderno del servicio. En SQL Server on-premises, hay que entender versión, edición, carga y consecuencias. Nada sustituye a probar con una carga representativa. Sí, representativa de verdad, no tres inserts en una tabla de juguete.

Además, controla el crecimiento del PVS o puedes estar activando una bomba lógica y no una optimización.

Recovery tras restore no es exactamente lo mismo que crash recovery

Conviene separar conceptos. Cuando restauramos una base, también hablamos de recovery. Pero no todo recovery viene de un crash. En un restore con NORECOVERY, SQL Server deja la base preparada para recibir más backups de log o diferenciales. Con RECOVERY, ejecuta la fase final que deja la base consistente y online.

En un crash recovery, SQL Server parte de los archivos existentes de una base que sufrió una parada no limpia. Por su parte, un restore, parte de una secuencia de backups que se aplican en orden. En ambos casos el log es esencial, pero el contexto operativo cambia.

Durante restore, Redo aplica cambios de backups de log para avanzar la base hasta un punto concreto. Undo revierte transacciones incompletas en ese punto final, salvo que dejemos la base en NORECOVERY para seguir aplicando más logs. Por eso una base en NORECOVERY no está disponible: todavía no se ha ejecutado la recuperación final completa que resolvería las transacciones abiertas en el punto restaurado.

Esto explica también el modo STANDBY, donde SQL Server permite acceso de solo lectura entre restores usando un undo file para poder revertir temporalmente transacciones no confirmadas y luego rehacerlas al aplicar más logs. Es una mecánica diferente, pero refuerza la misma idea, Redo y Undo son operaciones reales sobre la historia transaccional.

Recovery Pending y Suspect: cuando ni siquiera podemos empezar bien

No todas las recuperaciones terminan bien y ahí es cuando el DBA empieza a sudar. Pero tranquilo, antes de alarmarnos vamos a entender que es lo que está pasando.

Lo primero es que no todas las recuperaciones fallidas significan corrupción lógica. A veces SQL Server no puede iniciar recovery porque falta un fichero, no tiene permisos, el disco no está disponible o el log no se puede abrir. En esos casos podemos ver RECOVERY_PENDING. La base necesita recuperación, pero el motor no puede comenzarla.

SUSPECT es peor. Indica que SQL Server intentó recuperar la base y falló. Puede deberse a corrupción, problemas de I/O, inconsistencias graves o imposibilidad de completar el proceso. Aquí hay que respirar antes de hacer barbaridades. Poner la base en emergency mode y ejecutar DBCC CHECKDB pero con cabeza. REPAIR_ALLOW_DATA_LOSS no es un plan, es una última bala cuando no hay backup válido y ya hemos aceptado pérdida de datos. El propio nombre lo dice bastante claro, aunque parece que algunos leen solo hasta REPAIR.

El camino serio ante una base suspect empieza por revisar error log, visor de eventos, almacenamiento, disponibilidad de ficheros, backups, última restauración válida y DBCC CHECKDB. Si hay backup bueno, restaurar suele ser más profesional que improvisar.

Qué mirar cuando una base está recuperando

Cuando una base tarda en volver, yo empezaría por el error log. Ahí suele aparecer el avance de recovery y mensajes específicos de cada fase. Después miraría el estado de la base, si el proceso aparece en sys.dm_exec_requests, waits relevantes y métricas del log.

Un primer bloque sencillo sería:

Luego intentaría ver actividad asociada:

Y si la base permite consultar estadísticas de log:

No siempre tendrás toda la información disponible mientras la base está recuperando. Tampoco siempre tendrás permisos. Y lo peor, no siempre la DMV te dará una respuesta con la claridad que deseas. Pero este enfoque es infinitamente mejor que reiniciar el servicio “a ver si espabila”. Reiniciar un recovery largo sin entender qué está haciendo puede devolverte al mismo punto, solo que con más presión y menos tiempo o volver a empezar, lo que es peor.

El papel de las transacciones largas: el enemigo de siempre

Las transacciones largas son veneno para muchas cosas: bloqueos, version store, crecimiento del log, truncado del log, replicación, disponibilidad y recovery. En este contexto, son especialmente desagradables porque aumentan el trabajo de Undo y pueden mantener activa una parte antigua del log.

Una transacción abierta impide truncar log más allá de su LSN de inicio o de lo que necesite el motor para mantener consistencia. Si además modifica muchas páginas, genera una cadena larga de log records que habrá que deshacer si no llega a commit. Da igual que el servidor tenga discos rápidos y mucha memoria, Undo seguirá teniendo que respetar la historia.

Podemos detectar transacciones activas en una base en funcionamiento con DMVs como:

En un sistema sano, esta consulta no debería mostrar transacciones de horas salvo que haya una razón muy justificada. Y aviso: “el proceso siempre ha funcionado así” no es una razón justificada.

Recovery y alta disponibilidad: failover no significa gratis

En Always On Availability Groups, mirroring o escenarios similares, recovery también aparece en distintos momentos. Una réplica secundaria aplica log constantemente para mantenerse sincronizada. En un failover, la base debe quedar en un estado consistente para aceptar trabajo como primaria. Dependiendo del modo, del retraso, de la cola de redo y de la carga, esto puede tener impacto.

En Availability Groups hablamos mucho de send queue y redo queue. La cola de redo indica log recibido pero aún no aplicado en la réplica secundaria. Si esa cola es grande, el failover puede no ser tan inmediato como algunos prometen. Alta disponibilidad no significa que la física se haya rendido. Significa que has diseñado mecanismos para reducir indisponibilidad, siempre que el sistema pueda procesar el trabajo pendiente.

Este punto conecta directamente con lo anterior. Si generas log a una velocidad que las réplicas no pueden rehacer, acumulas deuda. Las operaciones masivas sin mirar redo queue, acumulan deuda. Si tienes almacenamiento lento en secundarias, acumulas deuda. Luego llega el failover y alguien pregunta por qué tarda. La respuesta suele estar en las métricas que nadie miró antes.

Qué prácticas reducen el dolor del recovery

La primera práctica es diseñar transacciones razonables. No metas millones de cambios en una única transacción si puedes trocear de forma segura. Tampoco dejes transacciones abiertas esperando interacción humana. Y mucho menos, envuelvas procesos batch enormes en una transacción global porque “así si falla se revierte todo”. Esa frase ha causado más logs inflamados que muchas malas configuraciones.

La segunda es dimensionar bien el transaction log. Un log debe tener tamaño suficiente para la carga normal y los picos previstos. Debe crecer en incrementos razonables, no en migajas de 1 MB para una base que mueve cientos de GB. Debe tener backups de log frecuentes si está en full recovery. Y debe vigilarse como un componente crítico, no como ese fichero incómodo que ocupa disco.

La tercera es controlar los checkpoints. En bases modernas, indirect checkpoints con TARGET_RECOVERY_TIME ayudan a regular el volumen de Redo esperado. Pero hay que entender su coste, escribir dirty pages de forma más agresiva puede incrementar I/O de fondo. No se configura mirando solo recovery, se configura mirando carga, almacenamiento y objetivos reales.

Podemos revisar el objetivo de recuperación por base con:

Y cambiarlo, si procede, así:

La cuarta es evaluar ADR donde tenga sentido. En sistemas con transacciones largas, rollbacks dolorosos o requisitos estrictos de disponibilidad tras failover, ADR puede ser una mejora enorme. Pero hay que medir espacio, PVS, carga de escritura y comportamiento global. Nada de activar cosas porque suenan modernas. 

La quinta es probar restores y escenarios de recuperación. No basta con tener backups. Hay que restaurarlos, medir tiempos, validar consistencia y entender cuánto tardan Redo y Undo en situaciones reales. Un backup que nunca se ha restaurado es una promesa. Y en producción, las promesas pesan poco cuando el CFO pregunta por la aplicación caída.

Conclusión

SQL Server recovery no es un proceso místico. Es una consecuencia directa de cómo el motor escribe cambios (primero log, después páginas), commit sin forzar data pages, páginas no confirmadas que pueden llegar a disco, recuperación basada en LSN, DPT, ATT, pageLSN y cadenas de log records.

Analysis reconstruye el mapa, define qué páginas podían estar sucias y qué transacciones estaban vivas. Redo repite la historia necesaria para que los data files alcancen un estado coherente con el log. Undo recorre hacia atrás las transacciones perdedoras y compensa sus efectos, registrando CLRs para que incluso el propio rollback sea recuperable.

Cuando entiendes esto, dejas de decir cosas como “Redo aplica solo commits” o “dirty page significa dato sin confirmar”. También dejas de creer que un checkpoint es una varita mágica o que reiniciar una instancia bloqueada en recovery es una estrategia. El motor sabe lo que tiene que hacer. La pregunta es si nosotros hemos diseñado el sistema para que ese trabajo sea razonable.

 

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

PostgreSQL vs SQL Server, ¿merece la pena migrar?

Cada cierto tiempo aparece la misma conversación en empresas que llevan años trabajando con SQL Server: “¿Y si migramos a PostgreSQL y nos ahorramos las licencias?”. La pregunta es legítima. Lo que no es legítimo es convertirla en una decisión de arquitectura basada únicamente en el coste de la licencia de SQL Server vs la de PostgreSQL gratis. PostgreSQL se distribuye bajo una licencia open source permisiva, similar a BSD o MIT, y eso elimina una parte importante del coste directo de producto. Pero eliminar una factura no elimina la complejidad técnica, ni la necesidad de soporte, ni la responsabilidad de que producción siga funcionando el lunes a las nueve.

Antes de hablar de migración conviene poner algo de orden, porque aquí se mezclan conceptos con una complejidad que no deberían tratarse sin supervisión adulta (y experta).

Licencias no es lo mismo que coste total

SQL Server tiene un modelo de licenciamiento que puede pesar mucho, especialmente en entornos con bastantes cores, virtualización, alta disponibilidad, entornos de contingencia y cargas críticas. Microsoft mantiene modelos por core, virtualización, Software Assurance, pago por uso y otros escenarios que hay que entender con lupa, café y, preferiblemente, alguien de licencias cerca. En Azure Arc, por ejemplo, Microsoft documenta opciones de licencia por vCore, core físico y virtualización ilimitada bajo determinadas condiciones. También contempla instancias pasivas para HA/DR en ciertos escenarios. No es precisamente “siguiente, siguiente, finalizar”.

PostgreSQL, por su parte, no te cobra por usar el motor comunitario. Pero alguien tiene que diseñar la arquitectura, mantenerla, parchearla, monitorizarla, hacer backups, probar restauraciones, resolver incidencias y explicar por qué una query que iba bien en SQL Server ahora ha decidido explorar su lado artístico. Ese alguien puede ser tu equipo interno, un proveedor especializado o un servicio gestionado. En todos los casos hay coste. Simplemente cambia de sitio. Y si, ya sé que me vais a decir que todo esto también lo necesitamos en SQL Server pero en este caso tenemos la opción de un soporte por debajo que no tenemos en PostgreSQL.

Esta es la primera trampa habitual, comparar la licencia de SQL Server contra cero. La comparación real debería ser SQL Server con su ecosistema, soporte, HA, herramientas y conocimiento interno frente a PostgreSQL con su ecosistema, soporte contratado o comunitario, nuevas herramientas, curva de aprendizaje y esfuerzo de migración.

Y sí, se puede ahorrar dinero. Mucho, incluso. Pero si el ahorro sale de dejar sin cubrir soporte, disponibilidad o mantenimiento, no es ahorro, es deuda técnica.

Cuando bajamos del Excel de compras a la producción real, la conversación empieza a ponerse interesante.

Soporte: nadie llama a “la comunidad” a las tres de la mañana

SQL Server tiene una política de ciclo de vida definida por Microsoft. Por ejemplo, SQL Server 2022, tiene soporte mainstream hasta enero de 2028 y soporte extendido hasta enero de 2033. SQL Server 2025 amplía ese horizonte, con soporte mainstream hasta enero de 2031 y extendido hasta enero de 2036.

PostgreSQL también tiene política de versiones, pero con otro enfoque. El PostgreSQL Global Development Group publica una versión mayor aproximadamente una vez al año y mantiene cada versión mayor durante cinco años. Después llega el final de vida y toca actualizar. En septiembre de 2026, PostgreSQL 18 es la rama estable actual y PostgreSQL 19 está en beta, no recomendado para producción.

Esto no es mejor ni peor por sí mismo. Es diferente. En SQL Server compras producto, soporte y un marco empresarial muy claro. En PostgreSQL tienes un motor excelente, una comunidad muy potente y un mercado amplio de proveedores, pero debes decidir quién te da soporte, con qué SLA, sobre qué distribución, en qué plataforma y con qué responsabilidad contractual.

Para una aplicación interna pequeña, quizá baste con conocimiento propio y una buena política de actualizaciones. Para una plataforma crítica, con penalizaciones por caída, auditoría y ventanas de mantenimiento ridículas, necesitas algo más serio que “lo miramos en Stack Overflow”. Que todos hemos pecado, pero tampoco hace falta convertirlo en estrategia corporativa.

Después viene la pregunta incómoda que muchos evitan hasta que ya es tarde: ¿cómo vamos a mantener la disponibilidad?

Alta disponibilidad: no basta con levantar una réplica y rezar

SQL Server tiene Always On Availability Groups como una solución integrada de alta disponibilidad y recuperación ante desastres. Permite agrupar bases de datos, conmutarlas juntas, usar réplicas síncronas o asíncronas, listener para redirigir conexiones y herramientas integradas de administración y monitorización. En Enterprise ofrece un conjunto bastante completo, en Standard existen Basic Availability Groups con limitaciones.

PostgreSQL también soporta alta disponibilidad mediante replicación, standby, streaming WAL, hot standby y otras piezas del ecosistema. La propia documentación habla de servidores que trabajan juntos para permitir que un secundario tome el control si falla el primario, o para repartir lecturas en ciertos escenarios.

La diferencia está en el empaquetado. En SQL Server muchas piezas vienen dentro de una solución reconocible, aunque luego haya que configurarla bien, que tampoco es magia negra pero se le parece. En PostgreSQL normalmente compones la arquitectura con varias herramientas: replicación nativa, Patroni o repmgr para failover, etcd o Consul para consenso, pgBackRest o Barman para backups, HAProxy o PgBouncer para conexiones, más monitorización específica.

¿Funciona? Sí. ¿Es robusto? Puede serlo muchísimo. ¿Es “instalo PostgreSQL y ya tengo Always On”? No. Y quien lo venda así probablemente te está engañando para venderte el servicio.

El punto no es que SQL Server gane siempre en HA ni que PostgreSQL sea artesanal. El punto es que la arquitectura objetivo debe diseñarse. Hay que definir RPO, RTO, failover automático o manual, fencing, split-brain, backups, restauraciones, pruebas de conmutación, impacto en conexiones y comportamiento de la aplicación. La base de datos no es solo el motor, es el servicio completo.

Hay otra lectura interesante si la conversación se produce en entornos cloud de Microsoft. PostgreSQL no obliga necesariamente a salir de Azure. A veces el debate se plantea como si la única alternativa a SQL Server fuese montar PostgreSQL a pelo en unas máquinas virtuales y encomendarse al santo patrón del `systemctl status`. No tiene por qué ser así.

PostgreSQL también vive en Azure, con sus propias reglas

Azure Database for PostgreSQL Flexible Server permite desplegar PostgreSQL como servicio gestionado, con opciones de alta disponibilidad en la misma zona o con redundancia entre zonas, creando una réplica standby físicamente separada del primario. Microsoft documenta también un SLA del 99,99% cuando se habilita la resiliencia zonal, con las limitaciones habituales que conviene leer antes de creer la disponibilidad que nos venden en una reunión comercial.

Esto cambia parte de la ecuación. Si ya estamos en Azure, puede tener sentido evaluar PostgreSQL gestionado en lugar de autogestionar el motor. Ganamos automatización, integración con Azure, backups gestionados, operación más cómoda y una vía más natural para equipos que ya trabajan con identidades, redes, monitorización y seguridad dentro de la plataforma de Microsoft. Pero no conviene confundir “servicio gestionado” con “servicio sin decisiones”. Sigue habiendo que diseñar conectividad, seguridad, parámetros, mantenimiento, réplicas, pruebas de recuperación, costes y observabilidad. Azure te quita trabajo operativo, no criterio técnico.

Y en esa misma línea aparece Azure HorizonDB, actualmente en preview. Microsoft lo presenta como un servicio de base de datos cloud-native, totalmente gestionado, preparado para cargas críticas y construido sobre PostgreSQL, con separación de cómputo y almacenamiento, diseño “database-as-log”, alta disponibilidad y capacidades orientadas a IA, incluyendo soporte para vectores e integración con Azure AI Foundry Tools.

¿Significa eso que HorizonDB debe entrar mañana en producción para sustituir cualquier SQL Server incómodo? No tan rápido. Que ponga PostgreSQL-compatible no significa que desaparezcan las pruebas de compatibilidad, rendimiento, comportamiento transaccional y operación. Además, al estar en preview, todavía arrastra limitaciones relevantes. Microsoft documenta, entre otras, la ausencia de réplicas de lectura entre regiones para recuperación ante desastres, retención de backup no configurable más allá del estado actual del servicio y disponibilidad regional limitada.

Y entonces llega la parte que suele romper presupuestos: el código.

T-SQL no se convierte en PL/pgSQL con incienso

Si la aplicación usa SQL Server como simple almacén relacional y apenas contiene lógica en base de datos, la migración puede ser razonable. Habrá que adaptar tipos de datos, sintaxis, funciones, paginación, fechas, identidades, esquemas, collations, bloqueos y alguna alegría más. Pero es abordable.

Si la aplicación tiene procedimientos almacenados, funciones escalares, funciones de tabla, triggers, jobs, SQL dinámico, cursores, transacciones complejas, `MERGE`, temporales, hints, `TRY…CATCH`, `OUTPUT`, `ROWVERSION`, `IDENTITY`, `SEQUENCE`, `XML`, `JSON`, `geography`, `hierarchyid` o dependencias con SQL Agent, entonces ya no estamos migrando una base de datos. Vamos a terminar reescribiendo una gran parte del sistema.

SQL Server define los procedimientos almacenados como grupos de instrucciones Transact-SQL o referencias a métodos CLR, con parámetros, salida y lógica procedural dentro del motor. PostgreSQL usa PL/pgSQL como lenguaje procedural para funciones, triggers y procedimientos, pero su modelo, sintaxis y comportamiento no son equivalentes uno a uno.

Un ejemplo muy típico: en SQL Server hay código que depende de tablas temporales, variables de tabla, `@@ROWCOUNT`, `SCOPE_IDENTITY()`, `GETDATE()`, `ISNULL()`, `TOP`, hints de bloqueo o comportamiento concreto de transacciones. En PostgreSQL encontrarás alternativas, pero no siempre sustitutos directos. A veces el cambio es trivial. A veces exige rediseñar.

Y luego está `MERGE`. SQL Server lo soporta desde hace años y PostgreSQL también lo tiene en versiones modernas, pero eso no significa que puedas copiar y pegar alegremente. La propia documentación de SQL Server advierte sobre cuestiones de rendimiento, concurrencia y pruebas exhaustivas en `MERGE`.

Cuanta más lógica viva dentro de SQL Server, más se parece la migración a un proyecto de desarrollo. No de infraestructura. De desarrollo.

Hay otro escenario que se usa mucho como comodín para tranquilizar conciencias: “no pasa nada, usamos ORM”.

ORM: el anestésico que no siempre duerme lo suficiente

Cuando todo el acceso a datos pasa por un ORM, la migración puede ser más sencilla, pero no automática. Un ORM ayuda a abstraer parte del código SQL, pero no elimina la semántica del motor. Las migraciones generadas, los tipos, las claves, los índices, las consultas LINQ o HQL complejas, el SQL nativo incrustado, los filtros globales, la paginación, los bloqueos optimistas y las estrategias de carga pueden comportarse de forma distinta.

Además, muchos proyectos dicen “todo va por ORM” hasta que alguien encuentra una carpeta llamada `LegacySql`, `RawQueries`, `Reports`, `CustomRepository`, `NoTocar`, o mi favorita: `Temporal`. Ahí dentro suele vivir media aplicación, un informe que factura millones y una query escrita por alguien que ya no trabaja en la empresa pero cuyo espíritu sigue bloqueando tablas.

Si el ORM genera SQL estándar y el modelo es limpio, perfecto. Pero hay que probar rendimiento real. SQL Server y PostgreSQL tienen optimizadores distintos, estadísticas distintas, planes distintos y patrones de indexación que no siempre coinciden. Un índice que tiene sentido en SQL Server no tiene por qué resolver igual en PostgreSQL, y viceversa.

También cambian los mecanismos de concurrencia. SQL Server puede trabajar con distintos niveles de aislamiento, row versioning mediante RCSI o snapshot, bloqueos con hints y un comportamiento que muchos equipos ya conocen. PostgreSQL usa MVCC de forma natural, con autovacuum como pieza crítica del mantenimiento. Ignorar autovacuum en PostgreSQL es como ignorar tempdb en SQL Server, puedes hacerlo, pero luego no pongas cara de sorpresa.

Por eso, incluso con ORM, una migración seria necesita pruebas funcionales, pruebas de carga, revisión de SQL generado, revisión de índices y validación de planes. El conector es solo una parte. Cambiarlo y celebrar es una magnífica forma de crear una incidencia.

La pregunta final es quizá la más importante: ¿hablamos de una aplicación existente o de un proyecto nuevo?

Migrar legacy no es lo mismo que empezar de cero

Para un proyecto nuevo, PostgreSQL puede ser una decisión magnífica. Permite diseñar desde el principio pensando en su modelo, sus tipos, su ecosistema, su estrategia de HA, su observabilidad y sus herramientas. El equipo desarrolla con PostgreSQL, prueba con PostgreSQL y despliega con PostgreSQL. No hay traducción cultural ni arqueología de procedimientos almacenados.

En una aplicación existente sobre SQL Server, la decisión cambia. Hay que inventariar objetos, dependencias, código T-SQL, jobs, integraciones, informes, ETLs, permisos, usuarios, linked servers, paquetes SSIS, mantenimiento, backups, auditoría, cifrado y automatización. También hay que medir cuánto conocimiento real tiene el equipo sobre SQL Server y cuánto tendrá sobre PostgreSQL después de la migración.

No es raro que el coste de licencia sea visible y el coste de reescritura esté escondido. La licencia aparece en una factura. El coste de migración aparece en retrasos, bugs, regresiones de rendimiento, noches raras y reuniones donde alguien dice “en preproducción iba bien”. Maravilloso epitafio técnico.

Una migración puede tener sentido cuando el ahorro recurrente compensa el esfuerzo inicial, cuando se quiere reducir dependencia de proveedor, cuando el equipo domina PostgreSQL o quiere invertir en ello, cuando la aplicación tiene una capa de datos razonablemente limpia, o cuando el producto necesita desplegarse en muchos clientes sin arrastrar licencias. Pero no debería justificarse solo con “PostgreSQL es gratis”.

También hay escenarios híbridos. Algunas aplicaciones nuevas pueden nacer en PostgreSQL mientras sistemas críticos existentes siguen en SQL Server. O se puede migrar por dominios, empezando por módulos menos acoplados. No todo tiene que resolverse con una migración big bang.

El criterio técnico debería ser bastante simple, aunque no siempre cómodo.

Conclusión: cambia de motor si tiene sentido, no porque la factura moleste

PostgreSQL es un motor excelente. SQL Server también. La comparación seria no va de religión tecnológica ni de camisetas de comunidad contra licencias corporativas. Va de coste total, soporte, disponibilidad, conocimiento, compatibilidad, código existente, rendimiento y riesgo operativo.

Si hablamos de un desarrollo nuevo, PostgreSQL merece estar en la mesa desde el primer minuto. Si hablamos de migrar una aplicación existente con años de T-SQL, procedimientos almacenados, jobs, informes y dependencias, hay que hacer inventario, pruebas y números de verdad. No números de PowerPoint, números de producción.

Migrar para ahorrar licencias puede ser una gran decisión. Migrar creyendo que basta con cambiar el conector es una invitación formal al desastre. Y producción, como ya sabemos, siempre acepta invitaciones.

 

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

Respuestas del sorteo de la European FabCon + SQLCon 2026 Barcelona

Hace unos días lanzamos en nuestra comunidad un sorteo muy especial. La organización de la European FabCon + SQLCon 2026 Barcelona nos regaló una entrada de tres días para sortear entre vosotros y decidimos que, ya que nos juntamos para hablar de datos, la entrada no iba a decidirse simplemente sacando un nombre de una urna virtual.

Había que responder correctamente cinco preguntas técnicas. Quienes acertaron las cinco entraron en el sorteo y ya tenemos ganadora: enhorabuena, Inés. Esperamos que disfrutes de los tres días de conferencia y, sobre todo, que vuelvas con unas cuantas ideas nuevas que poner a prueba.

También queremos agradecer a la organización de la European FabCon + SQLCon 2026 Barcelona que haya contado con nuestra comunidad y, por supuesto, a todos los que habéis participado. Las cinco preguntas tocaban búsqueda híbrida y RAG, SQL Database Projects y schema drift, Data API Builder con GraphQL, indexación de JSON y algo de manipulación de cadenas con T-SQL.

Ahora que el sorteo ha terminado podemos hacer la parte interesante, explicar las respuestas. Para ello voy a tirar de IA, es fin de semana y no me apetece trabajar más, lo siento. Pero, os prometo que la respuesta está validada y es correcta.

Pregunta 1: cómo combinar búsqueda vectorial y full-text sin mezclar puntuaciones incompatibles

La primera pregunta planteaba una aplicación RAG que recuperaba contexto mediante dos mecanismos: búsqueda full-text y búsqueda vectorial. Ambos devolvían resultados ordenados, pero sus puntuaciones utilizaban escalas completamente diferentes.

La respuesta correcta era A: combinar mediante Reciprocal Rank Fusion los rankings obtenidos mediante búsqueda vectorial y FREETEXTTABLE.

El detalle importante está en que Reciprocal Rank Fusion, o RRF, no necesita comparar directamente las puntuaciones originales. Trabaja con la posición que ocupa cada documento dentro de cada ranking.

Simplificando, cada posición aporta un valor parecido a:

Después sumamos las contribuciones de las distintas listas. Un documento situado muy arriba en la búsqueda vectorial y también bien colocado en full-text acumulará más puntuación que otro que solo destaque en una de ellas.

Eso resuelve precisamente el problema que planteábamos. Una distancia coseno, una similitud vectorial y el RANK producido por full-text no tienen por qué compartir significado ni distribución. Intentar sumar directamente esos valores sería comparar unidades que no representan lo mismo. RRF evita ese problema porque utiliza el orden relativo y no las puntuaciones en bruto. Microsoft describe precisamente RRF como un método score-free apropiado para fusionar búsquedas con distribuciones distintas, como BM25 y similitud vectorial.

¿Por qué FREETEXTTABLE y no CONTAINS? Porque FREETEXTTABLE devuelve KEY y RANK. En cambio, CONTAINS es un predicado: responde si una fila cumple o no la condición, pero no devuelve un ranking. CONTAINSTABLE sí lo haría, pero no era lo que decía la opción C.

La opción B, basada en percentiles y una media geométrica, podría formar parte de un sistema de scoring diseñado expresamente para un workload determinado, pero ya obliga a introducir una transformación y unas decisiones sobre las escalas. Justo lo que queríamos evitar.

La opción D tampoco fusiona realmente ambos rankings. Primero reduce el conjunto mediante vector search y después deja que full-text trabaje únicamente sobre esos candidatos. Un documento excelente léxicamente que haya quedado fuera del primer corte vectorial ya no tiene ninguna posibilidad de aparecer.

La clave era reconocer que teníamos dos rankings válidos, no dos puntuaciones directamente comparables.

Pregunta 2: detectar schema drift no significa borrar todo lo que no esté en el proyecto

La segunda pregunta trataba un problema bastante más operativo. Tenemos un SQL Database Project almacenado en GitHub y queremos detectar modificaciones realizadas directamente sobre producción. Sin embargo, hay objetos de producción gestionados por otros procesos y no queremos que cada despliegue los elimine alegremente.

La respuesta correcta era B: utilizar un DriftReport para detectar los cambios realizados fuera del proceso de despliegue y tratar ese control independientemente de DropObjectsNotInSource.

Aquí había que distinguir dos conceptos que se mezclan con demasiada facilidad: detectar diferencias y decidir qué hacer con ellas.

DriftReport informa de los cambios realizados en una base de datos registrada desde la última vez que se registró su estado. Es decir, nos proporciona un mecanismo para comprobar que aquello que creemos haber desplegado sigue correspondiéndose con la realidad de la base de datos. SqlPackage dispone además de BlockWhenDriftDetected, que permite impedir una actualización cuando detecta ese desfase.

DropObjectsNotInSource responde a otra pregunta. Indica si, durante la publicación, deben eliminarse del destino los objetos que no existen en el .dacpac. Microsoft lo documenta explícitamente como una propiedad de la operación de despliegue.

Por tanto, detectar drift no obliga a corregirlo automáticamente borrando objetos.

Ese era el problema de la opción A. Activar siempre DropObjectsNotInSource=True puede ser precisamente lo contrario de lo que necesitamos cuando existen objetos administrados fuera del proyecto. Una cosa es que el pipeline nos diga «producción ya no coincide con el estado registrado» y otra muy distinta responder «pues borremos todo lo que sobra». Automatizar una decisión equivocada solo consigue equivocarnos más deprisa.

La opción C también rompe el modelo. Si importamos producción antes de cada build y la convertimos automáticamente en nuestra fuente de verdad, los cambios manuales dejan de ser drift: acabamos incorporándolos al modelo. Git deja de definir el estado deseado y pasa a documentar lo que alguien haya decidido hacer en producción.

Y desactivar la validación del modelo, como proponía D, no soluciona absolutamente nada relacionado con el drift.

La lección es sencilla: observación y remediación deben ser decisiones separadas.

Pregunta 3: una FOREIGN KEY no define por sí sola el contrato GraphQL de Data API Builder

La tercera pregunta planteaba dos entidades, Customer y Order, expuestas mediante Data API Builder. Queríamos poder consultar un cliente y navegar desde GraphQL hasta sus pedidos.

La respuesta correcta era C: configurar las entidades y la relación que DAB va a exponer mediante GraphQL.

Aquí la trampa estaba en confundir el modelo relacional de la base de datos con el contrato de la API.

Una FOREIGN KEY describe una relación e impone integridad referencial en SQL. Data API Builder puede aprovechar esa información y, una vez que hemos definido una relación, incluso puede inferir los campos implicados cuando existe una clave foránea adecuada. Pero eso no significa que DAB publique automáticamente todas las relaciones que encuentre en la base de datos.

La documentación actual es bastante explícita: para poder navegar entre entidades hay que indicar a DAB cómo se relacionan mediante la sección relationships. La configuración establece, entre otras cosas, la entidad destino y la cardinalidad.

Ese matiz descarta la opción A. La FOREIGN KEY ayuda a implementar la relación, pero no sustituye su definición en la API.

La B falla por un motivo parecido. Exponer Customer no provoca que DAB descubra Order, la convierta mágicamente en otra entidad pública y modifique el esquema GraphQL durante la primera petición. Las entidades que DAB expone forman parte de su configuración.

La opción D se va al extremo contrario. No necesitamos crear obligatoriamente una vista con un JOIN. GraphQL en DAB permite modelar relaciones entre entidades y navegar por ellas, que era precisamente lo que pretendíamos hacer.

Esto además tiene una consecuencia arquitectónica importante. El esquema físico de la base de datos y la superficie que queremos publicar no son la misma cosa. No todas las relaciones existentes en SQL deberían convertirse automáticamente en navegación disponible para cualquier consumidor de una API. La configuración también forma parte de nuestra frontera de seguridad y de nuestro contrato externo.

La base de datos conoce las relaciones. La API decide cuáles expone.

Pregunta 4: cómo indexar una propiedad JSON en SQL Server 2022

La cuarta pregunta nos llevaba a SQL Server 2022. Una tabla contiene varios millones de filas y almacena JSON dentro de una columna nvarchar(max). La consulta filtra así:

Queremos que el motor pueda utilizar un índice.

La respuesta correcta era B: crear una columna calculada basada en JSON_VALUE y crear un índice sobre esa columna; no necesita ser PERSISTED necesariamente.

En SQL Server 2022 no podemos crear un índice directamente sobre una propiedad interna del documento JSON como hacemos en SQL Server 2025. La técnica documentada consiste en exponer esa propiedad mediante una columna calculada y construir sobre ella un índice convencional.

Por ejemplo, conceptualmente podríamos hacer:

Ahora viene uno de los detalles importantes de la pregunta: la columna calculada no tiene que ser PERSISTED para poder utilizar esta técnica. Microsoft muestra precisamente el ejemplo con una columna calculada no persistida. Además, si mantenemos en la consulta la misma expresión JSON_VALUE, el optimizador puede reconocer la equivalencia con la columna calculada y considerar su índice. No necesitamos obligatoriamente reescribir todas las consultas para hacer referencia al nombre de la nueva columna.

Eso descarta la C.

La A tampoco puede ser correcta porque estamos específicamente en SQL Server 2022. CREATE JSON INDEX pertenece a SQL Server 2025.

La D supone que un índice normal sobre Payload permite localizar una propiedad interna del JSON. No funciona así. Para un nvarchar(max), el índice no construye una estructura secundaria que conozca cada ruta JSON que guardamos dentro del texto.

Hay además un matiz de producción que el test no necesitaba para responder, pero sí deberíamos recordar. JSON_VALUE puede devolver nvarchar(4000), por lo que conviene estudiar el tipo y la longitud reales de la propiedad que vamos a indexar. Microsoft recomienda convertirla al tipo más pequeño apropiado cuando sea posible, porque una clave de índice innecesariamente grande tiene consecuencias de almacenamiento y puede llegar a provocar problemas en operaciones DML.

Y, como siempre, tener un índice disponible no significa que vaya a ser utilizado en todas las consultas. La selectividad, las columnas solicitadas y el coste de los posibles key lookups siguen formando parte de la decisión del optimizador.

El índice no deja de ser un índice porque haya JSON de por medio. Afortunadamente, tampoco adquiere poderes mágicos.

Pregunta 5: SUBSTRING, CHARINDEX y una pequeña trampa con las posiciones

La última pregunta parecía bastante más inocente:

Queríamos obtener:

La respuesta correcta era A.

Vamos a calcularlo porque aquí la diferencia entre las opciones era literalmente de un carácter.

LEN(@Email) devuelve 28. El carácter @ ocupa la posición 18, por lo que el dominio comienza en la posición 19.

Después necesitamos localizar el último punto. Una forma cómoda consiste en invertir la cadena:

En es.aserpme@..., el primer punto aparece en la posición 3. Por tanto, la longitud que queremos extraer es:

Empezamos en la posición 19 y extraemos siete caracteres:

Eso es exactamente lo que hace la opción A.

La opción B suma uno a la longitud y devuelve ocho caracteres, por lo que también captura el punto final: empresa..

La opción C comienza en la posición del propio @, no después de él. Ya hemos desplazado mal la extracción antes de empezar a discutir la longitud.

La D utiliza CHARINDEX('.', @Email) sobre la cadena original. El primer punto no es el que precede a es, sino el que aparece en roberto.carrancio. Por tanto, está utilizando una posición que pertenece al nombre de usuario para calcular dónde termina el dominio.

Esta pregunta tenía además una pequeña segunda lectura. La expresión resuelve el requisito concreto planteado, pero no deberíamos confundir este ejercicio de manipulación de cadenas con un parser universal de nombres de dominio. Direcciones con subdominios o sufijos como .co.uk requieren definir primero qué entendemos exactamente por «dominio» y «extensión».

Con cadenas también conviene acordar el requisito antes de perfeccionar el SUBSTRING. SQL Server todavía no adivina lo que queríamos decir.

Cinco respuestas y una misma idea: entender el comportamiento importa más que recordar la sintaxis

Las respuestas del sorteo de la European FabCon + SQLCon 2026 Barcelona eran, por tanto, A, B, C, B y A.

Pero ese resultado es la parte menos importante. Las cinco preguntas compartían una idea, muchas respuestas incorrectas eran técnicamente verosímiles si nos quedábamos solo con el nombre de una característica.

RRF no sirve porque «mezcle búsquedas», sino porque evita comparar scores incompatibles. DriftReport no es lo mismo que eliminar objetos no declarados. Una FOREIGN KEY no constituye automáticamente un contrato GraphQL. SQL Server 2022 puede indexar una expresión sobre JSON sin disponer todavía de CREATE JSON INDEX. Y una expresión con SUBSTRING funciona porque cuadran exactamente las posiciones, no porque tenga suficientes CHARINDEX como para intimidar al problema.

De nuevo, gracias a la organización de la European FabCon + SQLCon 2026 Barcelona por cedernos la entrada, gracias a todos los que habéis participado y enhorabuena a Ines por ganar el sorteo.

Nos quedamos con algo mejor que cinco letras correctas, el razonamiento que permite volver a responder cuando cambien la versión, el escenario o el enunciado. Y eso, a diferencia de memorizar un test, sí suele sobrevivir al siguiente cambio de producto.

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

Azure SQL activa backups automáticos inmutables: por fin también en Managed Instance

Ayer, 4 de agosto de 2026, Microsoft anunció la disponibilidad general de la inmutabilidad automática para los backups de Azure SQL Database y Azure SQL Managed Instance. El anuncio ha pasado bastante desapercibido, quizá porque no incluye una nueva SKU ni un botón azul que diga «Proteger». Sin embargo, desde el punto de vista de seguridad y continuidad de negocio, me parece una de las mejoras más relevantes que ha recibido el servicio en bastante tiempo.

En el blog ya hablamos de backups inmutables y de cómo proteger las copias frente al ransomware. Allí expliqué por qué una copia accesible y modificable con las mismas credenciales que el resto de la infraestructura no es una última línea de defensa, sino otro objetivo esperando turno. Ahora Azure SQL incorpora esa protección directamente en su servicio administrado de backup.

La novedad merece atención, pero también aclarar ciertas cosas. Microsoft no ha convertido todos los backups históricos en objetos inmutables durante años. Lo que ha activado es una protección automática para los backups recientes de recuperación a un momento dado.

Qué ha anunciado Microsoft exactamente

Azure SQL protegerá con inmutabilidad hasta los siete días más recientes de backups utilizados para Point-in-Time Restore, tanto en Azure SQL Database como en Azure SQL Managed Instance. La protección se habilita de forma predeterminada, no requiere cuentas de almacenamiento, políticas WORM, bloqueos de retención ni procesos de exportación, y no tiene coste adicional. Se aplica con independencia del periodo PITR configurado.

Esto último conviene leerlo sin prisas. Si nuestra retención PITR es de siete días o menos, todos esos backups quedarán protegidos. Si tenemos 35 días, solo los siete más recientes serán inmutables con esta primera versión. Los otros 28 días seguirán retenidos, pero no reciben esta protección automática.

Otro detalle es que Microsoft presenta la funcionalidad como disponible de forma general, aunque el despliegue se está realizando progresivamente en todas las regiones y tardará unas semanas en completarse. De momento tampoco existe una propiedad visible que permita comprobar el estado de inmutabilidad, llegará más adelante. Azure SQL Database Hyperscale queda fuera por ahora. 

Sí, has leído bien, disponibilidad general pero con despliegue pendiente y sin incluir indicador visible que nos diga si está o no activado. Así no se hacen las cosas, Microsoft, ¡así no!.

El valor de la medida se entiende mejor cuando dejamos de tratar el backup como una tarea de mantenimiento y lo consideramos un activo de seguridad.

Por qué la inmutabilidad cambia el escenario de seguridad

Un ransomware serio no se limita a cifrar la base de datos. Busca las copias, las consolas de administración, las cuentas privilegiadas y cualquier mecanismo que permita recuperar sin pagar. Si el atacante consigue borrar o alterar los puntos de restauración, el incidente puede convertirse en una pérdida definitiva de información.

La inmutabilidad introduce una barrera distinta al control de acceso tradicional. Un objeto protegido mediante un modelo WORM (Write Once Read Many) no puede modificarse ni eliminarse durante el periodo establecido. Esto reduce el impacto de unas credenciales administrativas comprometidas, de una automatización mal configurada o del operador que ejecuta una orden destructiva en el entorno equivocado porque todos los portales de Azure se parecen demasiado.

No sustituye al mínimo privilegio, a MFA, a la supervisión ni a las pruebas de restauración. Tampoco evita que un atacante robe información antes de cifrarla. Pero conserva puntos de recuperación recientes aunque alguien consiga capacidad administrativa suficiente para intentar eliminarlos. Esa diferencia, durante un incidente, es la autentica «resiliencia».

Hasta ahora, esa historia era muy distinta según utilizáramos Azure SQL Database o Azure SQL Managed Instance.

SQL Database ya tenía una vía; Managed Instance no

Azure SQL Database ya permitía configurar inmutabilidad sobre backups de Long-Term Retention. Podíamos aplicar una política temporal o un legal hold y bloquearla para impedir la modificación o eliminación de las copias durante el periodo correspondiente. Era una capacidad orientada principalmente a conservación prolongada, auditoría y cumplimiento.

Azure SQL Managed Instance, en cambio, no permitía (ni permite) configurar sus backups LTR como inmutables. Las copias de larga duración no podían modificarse, pero un usuario con permisos suficientes sí podía eliminarlas desde el portal, Azure CLI, PowerShell o la API REST. Microsoft documentaba como alternativa la creación de backups manuales `COPY_ONLY` y su almacenamiento en una cuenta propia de Azure Storage con una política de inmutabilidad.

La alternativa funcionaba, pero estaba lejos de ser transparente. Había que diseñar el almacenamiento, proteger el acceso, crear credenciales, programar los backups, supervisar los trabajos y controlar la retención. Porque crear una política de inmutabilidad que se puede desactivar con la misma cuenta comprometida es una forma especialmente sofisticada de no resolver nada.

Y todavía quedaba el detalle más incómodo: TDE.

Cómo había que hacerlo hasta ahora en Azure SQL Managed Instance

Azure SQL Managed Instance utiliza TDE de forma predeterminada. Cuando la base de datos está cifrada con una clave administrada por el servicio, no podemos generar un backup `COPY_ONLY`, porque la clave interna utilizada por Microsoft no se puede exportar. El backup se hace, si, pero luego no podría restaurarse fuera del flujo administrado del servicio, así que, podemos decir, que la operación no está soportada.

Para crear esas copias manuales teníamos que cambiar TDE y utilizar un protector administrado por el cliente, almacenado en Azure Key Vault o Managed HSM. Dicho de forma menos ceremonial, debíamos gestionar nuestra propia clave o certificado y conservar el material criptográfico necesario para restaurar. No bastaba con lanzar un `BACKUP DATABASE … WITH COPY_ONLY` y marcharnos satisfechos a actualizar el ticket.

Ese cambio añadía trabajo real. Había que configurar identidades y permisos sobre Key Vault, proteger la clave frente a borrados accidentales, definir su rotación, supervisar el acceso, documentar la recuperación y probar restauraciones. Una mala gestión del protector TDE puede dejar la base de datos inaccesible. 

Después había que enviar el backup a Blob Storage y aplicar allí una política de inmutabilidad correctamente bloqueada. Era una solución válida para organizaciones que necesitaban ese control, pero suponía una sobrecarga notable si el objetivo era evitar que los backups recientes desaparecieran durante un ataque.

La nueva funcionalidad elimina buena parte de esa complejidad para la ventana de recuperación inmediata.

El cambio operativo real

Con la inmutabilidad automática, Microsoft protege los backups administrados que Azure SQL ya utiliza para PITR. No necesitamos extraer la base de datos, mantener un trabajo propio, configurar un contenedor inmutable ni modificar TDE para abandonar la clave administrada por el servicio. Los procedimientos actuales de backup y restauración continúan funcionando sin cambios.

Para Azure SQL Managed Instance esto resulta especialmente importante. Por primera vez contamos con una capa nativa de inmutabilidad sobre los puntos recientes de recuperación sin asumir la cadena operativa del backup manual. En un incidente de ransomware, disponer de hasta siete días protegidos puede permitirnos restaurar a un punto anterior incluso cuando las credenciales administrativas hayan quedado comprometidas.

La ventana, sin embargo, obliga a pensar. Si el atacante permanece dentro del entorno durante semanas antes de ejecutar la fase destructiva, siete días pueden no ser suficientes. Tampoco bastan cuando la organización exige conservar copias inmutables durante meses o años. La mejora protege la recuperación operativa inmediata, pero no sustituye una política completa de conservación.

Y aquí entramos en el terreno donde seguridad y cumplimiento suelen encontrarse, aunque a los DBAs esta parte siempre nos resulte muy aburrida.

Cumplimiento normativo: ayuda mucho, pero no firma la auditoría

Muchas empresas no implementan backups inmutables porque el DBA haya tenido una revelación durante la ventana de mantenimiento. Lo hacen porque una norma sectorial, una auditoría, una póliza de ciberseguro o una política corporativa exige conservar copias resistentes a manipulación y borrado.

Microsoft basa esta protección en las capacidades de almacenamiento inmutable de Azure Storage y cita escenarios relacionados con SEC 17a-4(f), CFTC 1.31(d) y requisitos de conservación de FINRA. Es un control técnico relevante para gobierno, resiliencia y auditoría, pero no convierte por sí solo a la empresa en conforme con ninguna regulación. Cada organización debe validar sus obligaciones con sus equipos legales y de cumplimiento.

El auditor no debería preguntar únicamente si el backup es inmutable. También preguntará cuánto tiempo se conserva, quién puede restaurarlo, dónde se almacena, qué evidencias demuestran la protección y cuándo se probó la última recuperación. Contestar «Azure lo hace solo» puede funcionar en una reunión comercial, pero en una auditoría seria no va a colar.

Además, la imposibilidad actual de consultar una propiedad que confirme la inmutabilidad puede complicar la evidencia durante el despliegue inicial. Conviene documentar el anuncio, la cobertura esperada y la fecha en la que Microsoft complete la activación en nuestra región.

La diferencia entre siete días de protección y una estrategia completa sigue siendo importante.

Lo que esta novedad todavía no resuelve

Azure SQL Managed Instance continúa sin permitir configurar la inmutabilidad de sus backups LTR. Una empresa que necesite conservar copias inmutables durante uno, cinco, años o cualquier periodo superior a 7 días, seguirá necesitando una solución adicional. A día de hoy, el patrón documentado continúa siendo el backup `COPY_ONLY` a Blob Storage inmutable, con todo lo que implica para TDE, claves, automatización y pruebas de restauración.

La protección automática tampoco cubre Azure SQL Database Hyperscale en esta primera versión. Ni evita la corrupción lógica ya presente en los backups, ni detecta cuándo comenzó un ataque. Un backup inmutable de una base de datos dañada sigue siendo un backup dañado, solo que ahora nadie puede borrarlo. La inmutabilidad protege la copia, las pruebas de restauración demuestran que sirve.

Tampoco debemos confundir inmutabilidad con aislamiento total. Seguimos necesitando identidades protegidas, alertas, registros de actividad, separación de funciones y procedimientos de respuesta. No podemos confiar toda la seguridad a una cuenta con rol Owner compartida por cuatro administradores. 

La noticia cambia prioridades concretas y merece una revisión de nuestra arquitectura actual.

Qué haría yo a partir de ahora

Lo primero sería identificar qué bases de datos dependen exclusivamente de PITR y cuáles tienen requisitos de retención inmutable superiores a siete días. Para las primeras, esta funcionalidad reduce mucho el riesgo sin añadir trabajo. Para las segundas, debemos mantener la estrategia LTR correspondiente en Azure SQL Database o el mecanismo adicional implantado para Managed Instance.

También revisaría las instancias donde se cambió a TDE con clave administrada por el cliente únicamente para poder generar backups `COPY_ONLY` inmutables. La nueva protección puede permitir simplificar parte de esa arquitectura, pero no retiraría nada hasta confirmar que siete días cubren el riesgo y el cumplimiento aplicables. Cambiar el protector TDE porque ha aparecido una noticia interesante no es una tarea para hacer entre dos reuniones.

Por último, actualizaría la documentación de continuidad y los procedimientos de respuesta. El equipo debe saber qué periodo está protegido, cómo restaurar, qué ocurre con las copias anteriores y qué limitaciones siguen existiendo. La funcionalidad automática reduce operaciones, no elimina la necesidad de entenderlas.

La mejora es importante precisamente porque convierte una protección compleja en una capacidad predeterminada del servicio.

Conclusión

La inmutabilidad automática de los backups recientes refuerza de forma clara la seguridad de Azure SQL Database y, sobre todo, de Azure SQL Managed Instance. Protege hasta siete días de PITR sin coste adicional, sin políticas propias y sin obligarnos a modificar TDE para crear copias manuales.

No resuelve la retención inmutable de larga duración en Managed Instance ni sustituye una estrategia completa de recuperación. Pero cierra una carencia seria frente a ransomware, borrados malintencionados y credenciales comprometidas.

Ahora tenemos una última línea de defensa mejor. Toca documentarla, probarla y no estropearla creyendo que «automático» significa «no tengo que saber cómo funciona».

Enlaces de referencia: 

https://techcommunity.microsoft.com/blog/azuresqlblog/announcing-automatic-backup-immutability-for-azure-sql-database-and-azure-sql-ma/4543975

https://learn.microsoft.com/es-es/azure/storage/blobs/immutable-storage-overview

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

Búsqueda híbrida en SQL Server: Combinando FULLTEXT y Vectores

Llevamos años viendo aplicaciones que convierten la busqueda de textos en una trituradora de CPU. El usuario escribe tres palabras, la capa de aplicación las trocea como puede, el ORM genera una consulta digna de un informe forense y SQL Server acaba haciendo lo que siempre hace, intentar salvarnos de nuestras propias decisiones. A veces lo consigue. A veces no. Y cuando no lo consigue, alguien abre una incidencia diciendo que “la base de datos va lenta”, como si el motor hubiera decidido por iniciativa propia montar un festival de LIKE ‘%texto%’.

La búsqueda de texto completo ha sido durante mucho tiempo nuestra herramienta principal para resolver este tipo de problemas. Los índices full-text permiten buscar términos dentro de columnas extensas, ordenar por relevancia y evitar que cada búsqueda acabe en un escaneo indecente. Ya hablé de esto con más detalle en el artículo sobre índices de texto completo o full-text indexes, porque siguen siendo una herramienta muy válida cuando necesitamos precisión léxica.

Pero ahora tenemos otro actor en escena: la búsqueda vectorial. SQL Server 2025, Azure SQL Database y SQL Database en Fabric incorporan capacidades nativas para trabajar con vectores, calcular distancias y ejecutar búsquedas aproximadas de vecinos más cercanos mediante VECTOR_SEARCH. En SQL Server 2025 estas capacidades están en vista previa y requieren habilitar PREVIEW_FEATURES. Además, Microsoft documenta que la versión más reciente de los índices vectoriales está disponible actualmente en Azure SQL Database y SQL Database en Microsoft Fabric.

La tentación evidente consiste en pensar que la búsqueda vectorial sustituye al full-text. Es una conclusión cómoda y moderna, pero equivocada. Es muy de presentación ejecutiva, lo que debería hacernos sospechar inmediatamente. La realidad es bastante más interesante, full-text y vectores resuelven problemas distintos, y la búsqueda híbrida aparece precisamente cuando dejamos de tratarlos como enemigos.

Antes de lanzarnos a escribir T-SQL como si no hubiera mañana, conviene dejar claro el problema de fondo.

La búsqueda híbrida no es IA pegada con cinta americana

La búsqueda híbrida combina una búsqueda léxica tradicional con una búsqueda semántica vectorial y fusiona después ambos conjuntos de resultados mediante una técnica de reordenación. La idea parece sencilla, una rama encuentra coincidencias de palabras con full-text indexes, la otra encuentra documentos semánticamente cercanos mediante comparación de embeddings, y una fase final decide qué documentos merecen aparecer arriba.

La clave está en esa última frase. No basta con ejecutar dos consultas y hacer un UNION. Eso no es búsqueda híbrida. Tampoco basta con sumar una puntuación full-text y una distancia vectorial, porque ambas magnitudes viven en universos matemáticos diferentes. SQL Server no va a convertir una escala relativa de relevancia textual y una distancia de coseno en una verdad universal solo porque usemos alias bonitos.

El full-text devuelve señales de relevancia asociadas a coincidencias lingüísticas. CONTAINSTABLE y FREETEXTTABLE devuelven una columna RANK con valores ordinales entre 0 y 1000, pero esos valores indican orden relativo dentro del conjunto devuelto. Microsoft advierte que el valor real no debe tratarse como una puntuación absoluta y puede variar entre ejecuciones.

La búsqueda vectorial, en cambio, devuelve una distancia. Si usamos coseno, valores más bajos indican mayor cercanía cuando trabajamos con VECTOR_DISTANCE o con la columna distance generada por VECTOR_SEARCH. Esa distancia expresa proximidad geométrica entre embeddings, no coincidencia literal de términos. Comparar directamente ambas señales es como comparar páginas leídas con cuanto agua está lloviendo fuera. Puedes hacerlo, pero no significa nada útil.

Por eso la búsqueda híbrida necesita una fase de fusión por rango. No fusionamos puntuaciones brutas, fusionamos posiciones. Y ahí entra Reciprocal Rank Fusion, más conocido como RRF.

Para entender por qué esto importa tanto, primero hay que asumir que full-text sigue siendo necesario.

Full-text: precisión léxica, no comprensión semántica

Un índice full-text sigue siendo una pieza excelente cuando necesitamos encontrar términos, variantes lingüísticas, prefijos, expresiones ponderadas o documentos donde aparecen palabras concretas. No es una tecnología vieja por no llevar “AI” en el nombre. De hecho, suele ser mucho más útil que una demo de embeddings cuando el usuario busca un código, una referencia contractual, un identificador de error o una cláusula exacta.

Imaginemos una base de conocimiento de soporte. Si el usuario busca OOM-802, lo normal es que quiera documentos que contengan OOM-802. No quiere artículos “conceptualmente relacionados con problemas de memoria”. Quiere ese código. Exactamente ese. Si le devolvemos un documento sobre “optimización general de memoria en servidores de aplicaciones” porque está cerca en el espacio vectorial, hemos hecho una búsqueda muy sofisticada y muy inútil. Todo muy moderno, todo muy mal.

Búsqueda de texto completo

Aquí full-text brilla. CONTAINSTABLE permite buscar en una columna indexada y obtener una lista ordenada por relevancia. Además, podemos limitar el volumen con el parámetro top_n_by_rank, algo especialmente importante cuando la condición puede devolver decenas o cientos de miles de filas. La documentación de Microsoft indica que limitar resultados con top_n_by_rank puede mejorar drásticamente el rendimiento, porque SQL Server ordena las coincidencias por rango y devuelve solo las primeras.

Esto no significa que full-text entienda el significado. Si buscamos “vehículo económico”, no podemos asumir que devuelva “coche barato”. Puede haber cierta ayuda lingüística según idioma, tesauro y configuración, pero no estamos ante comprensión semántica general. Full-text trabaja con términos, proximidad, formas lingüísticas y ranking textual. Los embeddings trabajan con representación numérica del significado. Mezclarlos sin entender esta diferencia es una forma estupenda de construir una solución que falle justo cuando el usuario la necesite.

En artículos anteriores del blog ya traté los índices de texto completo desde la perspectiva del DBA. Esa base sigue siendo necesaria. La búsqueda híbrida no elimina el conocimiento de full-text, lo potencia. Si alguien quiere saltar directamente a vectores sin entender CONTAINS, FREETEXT, CONTAINSTABLE, catálogos full-text, stopwords y ranking, adelante. Pero luego no vale llorar.

La otra mitad del problema está en la búsqueda semántica.

Vectores: significado aproximado, no magia contractual

Un embedding representa un texto como un vector numérico. La idea práctica es que textos con significado parecido generen vectores cercanos. A partir de ahí, podemos medir distancias mediante coseno, producto escalar o distancia euclídea, y recuperar los documentos más cercanos al vector de consulta. SQL Server permite realizar búsquedas exactas con VECTOR_DISTANCE, calculando la distancia entre el vector de búsqueda y los vectores almacenados. Microsoft describe este enfoque exacto como un cálculo exhaustivo que garantiza recuperar los vecinos más cercanos, aunque puede resultar costoso en conjuntos grandes.

La búsqueda exacta tiene una ventaja fundamental, no aproxima. Calcula. Si filtramos primero por cliente, estado, idioma, producto o fecha, y después aplicamos VECTOR_DISTANCE sobre un conjunto pequeño, podemos obtener resultados muy buenos sin necesidad de crear un índice vectorial. Esta estrategia suele ser más sensata de lo que parece, sobre todo en tablas medianas o en escenarios donde los filtros relacionales reducen mucho el número de candidatos.

La búsqueda aproximada, por su parte, usa estructuras especializadas para evitar comparar contra todos los vectores. En SQL Server y Azure SQL hablamos de VECTOR_SEARCH, que busca vectores similares mediante un algoritmo aproximado de vecinos más cercanos. Con la versión más reciente de los índices vectoriales, la sintaxis recomendada usa SELECT TOP (N) WITH APPROXIMATE y deja atrás el antiguo parámetro TOP_N, que Microsoft marca como obsoleto para índices recientes.

Índices vectoriales: grafos de embeddings

Aquí aparece DiskANN. Un índice vectorial de tipo DiskANN acelera la recuperación aproximada navegando una estructura de grafo pensada para localizar vecinos cercanos sin recorrer todo el conjunto. Eso tiene un coste. Hay mantenimiento, requisitos, restricciones y decisiones de diseño. No creamos un índice vectorial porque la palabra “vector” aparezca en una reunión. Lo creamos cuando el volumen, la latencia esperada y el patrón de consulta lo justifican.

En el artículo sobre DiskANN y búsqueda vectorial en SQL Server 2025 ya expliqué por qué no conviene indexar vectores en cualquier tabla. Esta recomendación sigue siendo válida. Si tienes 3.000 filas y filtros buenos, probablemente VECTOR_DISTANCE con búsqueda exacta te dé una solución más simple, más predecible y más fácil de razonar. La complejidad innecesaria siempre encuentra la forma de facturarse más tarde.

Como véis, la búsqueda semántica resuelve muy bien consultas ambiguas. “Portátil ligero para viajar”, “incidencia parecida a error de memoria”, “cliente molesto por retrasos recurrentes” o “documentación sobre migración sin parada” son ejemplos donde la coincidencia literal se queda corta. Pero esa misma flexibilidad puede volverse peligrosa cuando la consulta contiene un literal crítico.

Por eso necesitamos una arquitectura híbrida.

El patrón correcto: recuperar candidatos y fusionar rangos

La búsqueda híbrida no debería ejecutar una búsqueda vectorial contra toda la tabla y otra full-text sin límite para después cruzar todo alegremente. Eso solo consumiría recursos a lo loco. El patrón correcto consiste en recuperar un número razonable de candidatos por cada rama y fusionar después esos candidatos mediante un ranking controlado.

La rama léxica puede usar CONTAINSTABLE o FREETEXTTABLE, según queramos una búsqueda más precisa o más lingüística. Para códigos, identificadores, términos técnicos y mensajes de error, normalmente prefiero CONTAINSTABLE. Para búsquedas más naturales o descriptivas, FREETEXTTABLE puede encajar mejor. No son intercambiables, aunque en muchas aplicaciones se usen como si lo fueran. Luego nos extraña que el buscador parezca tener resaca.

La rama semántica puede usar VECTOR_DISTANCE o VECTOR_SEARCH. Si queremos una búsqueda exacta sobre pocos candidatos, VECTOR_DISTANCE es una buena opción. Si tenemos cientos de miles o millones de embeddings, baja tolerancia a latencia y un índice vectorial adecuado, VECTOR_SEARCH pasa a tener sentido. Microsoft también documenta que los índices vectoriales recientes aplican predicados de WHERE durante el proceso de búsqueda vectorial mediante filtrado iterativo, no simplemente después de recuperar vecinos, pero eso, de momento solo está en Azure.

Unir resultados 

La fase final no debe sumar RANK full-text con distancia vectorial. Debe convertir cada lista en posiciones ordinales y aplicar RRF. La fórmula general es sencilla:

score(d) = Σ 1 / (k + rank_i(d))

Donde rank_i(d) es la posición del documento d en la lista i, y k es una constante de suavizado. Si un documento no aparece en una rama, esa rama aporta cero. No aporta un rango inventado de diez mil, salvo que queramos aplicar una penalización heurística deliberada. RRF canónico suma contribuciones de listas donde el documento existe, lo demás ya es cocina propia.

El valor k = 60 aparece a menudo como constante práctica. No es una ley divina, ni una recomendación que debamos tatuarnos pero si podríamos decir que es un estándar del mercado. Un k más bajo da más peso a las primeras posiciones; un k más alto suaviza la diferencia entre posiciones. En buscadores internos suelo empezar con 60, medir, revisar consultas reales y ajustar. Sí, medir. Esa costumbre extravagante que a veces evita reuniones.

Vamos a bajarlo a código.

Modelo de datos para un escenario realista

Supongamos una tabla de tickets de soporte. Cada ticket tiene una descripción textual, metadatos relacionales y un embedding generado a partir del contenido que queremos buscar. No voy a centrarme aquí en la generación del embedding porque ya traté las búsquedas semánticas con IA en SQL Server 2025 en otro artículo, y además dejé ejemplos prácticos en YouTube para quien quiera ver el flujo completo con más calma.

La tabla podría tener esta forma simplificada:

El índice relacional no es decorativo. En una arquitectura híbrida seria, los filtros siguen importando. Si buscamos tickets de un cliente concreto, de un producto concreto o de un rango temporal, no queremos que la parte vectorial se comporte como un turista perdido recorriendo toda la ciudad. Queremos acotar el problema antes de calcular distancias.

El índice full-text iría sobre las columnas textuales relevantes. En producción hay que cuidar idioma, catálogos, stoplists y fragmentación del índice full-text, pero el esqueleto sería este:

El LANGUAGE 3082 corresponde al español. En entornos multidioma, este detalle se complica bastante. Un catálogo global con textos mezclados en cinco idiomas y una sola configuración lingüística no es internacionalización; es una ruleta. Si el negocio necesita búsquedas multidioma, hay que diseñar para ello, no rezar para que el motor “lo entienda”.

Para la parte vectorial, un índice DiskANN puede crearse así cuando el volumen lo justifique:

Los índices vectoriales recientes exigen al menos 100 filas con vectores no nulos antes de poder crearse, requieren una clave primaria clustered y tienen limitaciones actuales como ausencia de particionado, restricciones con TRUNCATE TABLE, no replicarse a suscriptores y problemas de despliegue directo con DacPac o BACPAC porque el índice se crea antes de cargar datos.

Esto ya nos da una pista importante: no hablamos de un índice cualquiera. No es un IX_Tabla_Campo que añadimos a las tres de la tarde para callar una alerta. Un índice vectorial afecta al ciclo de vida de la tabla, al despliegue, al mantenimiento y a los procesos de carga. Si el plan de despliegue de tu base de datos ignora esto, no tienes un plan; tienes un problema.

Ahora sí, veamos una búsqueda híbrida exacta.

Búsqueda híbrida exacta con VECTOR_DISTANCE

El primer patrón usa VECTOR_DISTANCE. Es el más fácil de razonar porque calcula distancias reales contra los candidatos considerados. Conviene usarlo cuando el conjunto final no es enorme, cuando los filtros relacionales son selectivos o cuando la precisión importa más que la latencia extrema.

El siguiente ejemplo recibe una consulta textual, genera su embedding y combina la rama semántica con la rama full-text. Limitamos ambas ramas para no fusionar medio millón de filas, es la forma de decirle a SQL “no hagas barbaridades”.

Hay varias decisiones intencionadas aquí. La rama semántica no calcula distancia contra toda la tabla, sino contra CandidatosFiltrados. Esto permite que el optimizador use índices relacionales antes de aplicar la parte matemática. Si el cliente tiene 4.000 tickets abiertos del producto, calcular 4.000 distancias puede ser razonable. Si tiene 40 millones, probablemente estamos diseñando otra cosa.

La rama léxica usa CONTAINSTABLE con top_n_by_rank. Ese cuarto parámetro limita el número de resultados full-text devueltos. No quiero traer todos los tickets que contengan “memoria” para después descubrir que el usuario solo verá 20 filas. Esa es la diferencia entre una consulta y un castigo.

La fusión usa FULL OUTER JOIN porque un documento puede aparecer solo en la rama semántica o solo en la rama léxica. Después, COALESCE(1.0 / (@rrf_k + rango), 0.0) suma cero cuando el documento no aparece en una rama. Esto es importante, no inventamos rangos artificiales. Si queremos penalizar más o menos los resultados huérfanos, podemos hacerlo, pero entonces debemos reconocer que estamos introduciendo una heurística adicional.

También ordeno por criterios secundarios. En producción, los empates ocurren. Si no defines desempates, el motor puede devolverte órdenes distintos entre ejecuciones. Y cuando el usuario dice “ayer salía primero”, a nadie le divierte explicar que una ordenación parcial no garantiza estabilidad. Bueno, a nadie normal, que yo estoy aquí preparando el artículo más largo de todos los que he escrito para explicaros eso.

Este patrón exacto es muy útil cuando trabajamos con filtros fuertes. Pero si el volumen semántico es grande y necesitamos latencia baja, toca pasar a búsqueda aproximada.

Búsqueda híbrida aproximada con VECTOR_SEARCH

VECTOR_SEARCH representa el patrón de búsqueda vectorial aproximada. Con índices vectoriales recientes, la sintaxis exige SELECT TOP (N) WITH APPROXIMATE y ordenación ascendente por la columna distance, que es la única clave de ordenación válida para los resultados aproximados. Si existe un índice ANN compatible con la misma métrica y columna, puede usarse, si no existe, el motor puede recurrir a kNN y emitir una advertencia.

Una versión híbrida aproximada puede quedar así:

Este ejemplo es parecido al anterior, pero cambia la rama semántica. Ahora usamos VECTOR_SEARCH, que devuelve la columna distance. La consulta solicita explícitamente resultados aproximados con WITH APPROXIMATE. Esto no es un adorno sintáctico, en índices recientes, Microsoft separa claramente la búsqueda aproximada de la exacta mediante esta sintaxis.

La presencia del WHERE dentro de la rama semántica también importa. En versiones recientes de índices vectoriales, Microsoft documenta filtrado iterativo, es decir, los predicados se aplican durante el proceso de búsqueda vectorial, no únicamente después. En versiones antiguas, los filtros podían aplicarse solo después de recuperar vecinos, lo que provocaba resultados vacíos o incompletos si los primeros vecinos no cumplían el filtro. Esta diferencia no es teórica, afecta directamente a la calidad del resultado.

Si trabajas con índices vectoriales antiguos, conviene migrarlos. Microsoft indica que los índices creados con estructuras anteriores se mantienen en la versión actual, pero quedarán retirados en una versión futura, y para obtener las capacidades recientes hay que eliminar y recrear el índice. La propia DMV sys.vector_indexes, junto con sys.indexes y sys.tables, permite consultar la versión registrada en los parámetros de construcción.

Esto introduce una responsabilidad nueva para el DBA. Ya no basta con saber si el índice existe. Hay que saber qué versión tiene, qué sintaxis admite, cómo se comporta con filtros y si soporta mantenimiento DML completo. El “tenemos índice vectorial” se parece demasiado al “tenemos backup”: la frase tranquiliza hasta que preguntas cuándo se ha probado.

RRF con pesos: cuando no todas las señales valen lo mismo

RRF básico da el mismo peso a cada rama. Eso suele ser un buen punto de partida, porque evita discusiones prematuras. Pero no todos los dominios tienen la misma necesidad de precisión literal y contexto semántico. En una base de conocimiento técnica, los códigos de error, nombres de procedimiento, versiones y referencias internas suelen pesar mucho. En un catálogo de productos o una búsqueda documental abierta, la semántica puede tener más protagonismo.

Podemos introducir pesos sin romper la estructura:

Esto ya no es RRF puro, sino RRF ponderado. No hay problema, siempre que seamos conscientes de ello. Lo peligroso no es ajustar el algoritmo, lo peligroso es hacerlo sin medir. Si damos demasiado peso a la rama semántica, un literal crítico puede quedar enterrado bajo documentos conceptualmente parecidos. Si damos demasiado peso a la rama léxica, perdemos recall semántico y volvemos al mundo de “no encuentro coche barato porque el documento dice vehículo económico”.

En entornos serios conviene registrar consultas reales, resultados seleccionados, clics, aperturas, tiempo hasta resolver la incidencia y feedback explícito si existe. Después podemos comparar configuraciones de pesos, tamaños de candidatos y valores de k. La relevancia no se calibra en una pizarra. Se calibra con datos y pruebas. Qué concepto tan revolucionario.

También podemos modificar pesos en función de la consulta. Si detectamos patrones de códigos, identificadores, GUIDs, números de versión o referencias internas, podemos favorecer full-text. Si la consulta es larga, descriptiva y sin literales críticos, podemos favorecer semántica. Este tipo de adaptación suele dar mejores resultados que una configuración fija para todos los casos.

La detección no tiene por qué ser perfecta. Basta con aplicar reglas prudentes. Una consulta con OOM-802, SQLSTATE 40001, KB500 o Procedure dbo.X probablemente necesita más precisión léxica. Una consulta como “el agente se queda bloqueado después de actualizar y consume mucha memoria” puede beneficiarse más del vector. El usuario no nos entrega una intención formal; nos entrega una frase. Nuestro trabajo consiste en no destrozarla.

El tamaño de los candidatos importa más de lo que parece

Uno de los errores más comunes en búsqueda híbrida consiste en recuperar pocos candidatos por rama. Si pedimos top 10 semántico y top 10 léxico para devolver top 10 final, apenas damos margen al algoritmo de fusión. RRF necesita espacio para encontrar documentos que quizá no sean número uno en ninguna rama, pero sí sean buenos en ambas.

Un documento que aparece en posición 12 en la rama semántica y posición 8 en la rama léxica puede ser mucho mejor resultado global que otro que aparece en posición 1 semántica y no aparece en léxica. Si solo recuperamos diez candidatos por rama, el primero puede sobrevivir y el segundo puede ni siquiera existir en la fusión. El algoritmo no puede ordenar documentos que nunca le hemos dado.

Por eso suelo trabajar con una ventana de candidatos bastante mayor que el resultado final. Si voy a mostrar 20 resultados, empezar con 100 o 200 por rama suele ser razonable. Luego hay que medir. No es lo mismo una tabla de tickets internos que una base documental de millones de artículos. Tampoco es lo mismo una búsqueda con filtros por cliente que un buscador global público.

El parámetro top_n_by_rank de full-text ayuda en la rama léxica. En la rama vectorial aproximada, TOP (N) WITH APPROXIMATE marca la ventana semántica. En la rama exacta con VECTOR_DISTANCE, TOP (N) limita el cálculo ordenado final, aunque el coste previo depende de cuántas filas sobrevivan a los filtros relacionales.

Aquí conviene vigilar planes de ejecución, memoria concedida, spills, paralelismo y lecturas lógicas. También hay que observar la distribución de distancias. Si todos los documentos tienen distancias muy parecidas, la señal semántica quizá no discrimina bien. Si el full-text devuelve rankings muy planos, la parte léxica quizá necesita mejorar sintaxis, tesauro, stopwords o normalización de contenido.

La búsqueda híbrida no elimina el tuning. Le añade dimensiones nuevas. Por si alguien echaba de menos complicarse la vida.

Exact search frente a ANN: elegir sin postureo

La búsqueda exacta con VECTOR_DISTANCE calcula distancias y ordena. Es simple, precisa y fácil de auditar. Si el conjunto candidato es pequeño, suele ser una gran elección. También es buena para validación, pruebas de calidad y comparación contra la búsqueda aproximada. En escenarios donde la exactitud es crítica, tener un modo exacto ayuda a detectar pérdida de recall introducida por ANN.

La búsqueda aproximada con VECTOR_SEARCH sacrifica exactitud teórica por velocidad. No tiene nada de malo. Muchos sistemas de búsqueda funcionan así porque el usuario necesita respuestas rápidas y suficientemente buenas. El problema aparece cuando alguien vende “aproximado” como si significara “igual pero más rápido”. No. Significa aproximado. La palabra está ahí, generosamente visible.

Microsoft documenta que VECTOR_SEARCH utiliza búsqueda aproximada de vecinos más cercanos, y que con índices recientes el optimizador puede decidir entre usar DiskANN o búsqueda kNN según las características de la consulta. También indica que, si no hay un índice ANN compatible con la métrica y la columna, se genera una advertencia y se usa kNN.

Esto tiene implicaciones operativas. Si una consulta esperada como aproximada cae a kNN por no tener índice compatible, puede cambiar radicalmente su coste. Si alguien crea el índice con métrica DOT y la consulta usa cosine, no esperes milagros. El motor no va a inventar un índice compatible porque el ticket esté marcado como urgente.

La métrica debe estar alineada con el modelo de embeddings y con la normalización aplicada. En muchos modelos textuales se usa coseno, pero no conviene asumirlo por costumbre. Hay que revisar cómo se generaron los embeddings, si están normalizados y qué distancia recomienda el proveedor o el patrón de uso. El DBA no tiene por qué entrenar el modelo, pero sí debe entender qué está almacenando y cómo se consulta.

Además, el índice vectorial no sustituye los índices relacionales. Microsoft recomienda combinar índices vectoriales con índices tradicionales en columnas usadas por filtros, especialmente cuando el filtrado iterativo puede aprovechar predicados selectivos.

Dicho de otra forma, seguimos necesitando buenos índices B-Tree. La IA no ha derogado la selectividad. Qué disgusto para algunos.

DML, versiones de índice y falsas verdades que caducan

Durante las primeras versiones, una de las limitaciones más agresivas de los índices vectoriales era que las tablas quedaban de solo lectura después de crear el índice. Para permitir DML había que aceptar índices obsoletos mediante configuración, con el riesgo de resultados no actualizados. Esa limitación existió y era importante.

Pero no conviene escribir arquitectura en piedra cuando la tecnología está en vista previa y cambia deprisa. Microsoft documenta que los índices vectoriales creados con la versión más reciente eliminan esa restricción: soportan INSERT, UPDATE, DELETE y MERGE, con mantenimiento automático y en tiempo real del índice. Los cambios son visibles para las búsquedas vectoriales después del commit.

Esto no significa que podamos olvidarnos del mantenimiento. Para reemplazos masivos de datos, por ejemplo al regenerar embeddings con otro modelo, Microsoft recomienda considerar eliminar y recrear el índice después de la carga para mantener calidad de búsqueda predecible. La estructura del índice se construyó con una distribución vectorial determinada; si cambiamos casi todos los embeddings, no deberíamos sorprendernos de que la calidad se resienta.

También siguen existiendo restricciones actuales. No hay soporte de particionado para índices vectoriales, la tabla necesita clave primaria clustered, TRUNCATE TABLE no puede ejecutarse directamente sobre tablas con índice vectorial y los despliegues con DacPac o BACPAC requieren cuidado porque el índice necesita datos no nulos existentes al crearse.

Esto cambia bastante el diseño de procesos ETL. Si regeneramos embeddings en bloque, quizá convenga cargar en una tabla staging, validar dimensiones, controlar nulos, medir duplicados y después hacer intercambio lógico. Si trabajamos en Azure SQL o Fabric con índices recientes, no tenemos que congelar la tabla para cada operación DML normal, pero sí debemos diseñar cargas masivas con cabeza.

También hay que monitorizar. La documentación menciona sys.dm_db_vector_indexes para observar salud del índice y tareas de mantenimiento. En sistemas donde el embedding cambia con frecuencia, esa DMV deja de ser curiosidad y pasa a ser parte del cuadro de mando operativo.

La conclusión práctica es sencilla, no repitas limitaciones antiguas sin mirar versión, pero tampoco vendas los índices vectoriales como índices normales. Ambas posturas son malas. Una por anticuada; la otra por ingenua.

Calidad de datos: embeddings duplicados y basura semántica

La búsqueda vectorial amplifica la calidad del contenido. Si generas embeddings sobre textos pobres, duplicados, genéricos o mal normalizados, el índice devolverá resultados pobres, duplicados, genéricos o mal normalizados. No hay misterio. El embedding no convierte documentación basura en conocimiento. Solo la representa numéricamente con mucha dignidad matemática.

Microsoft recomienda evitar conjuntos con alta proporción de embeddings duplicados, porque perjudican la calidad de resultados, desplazan vecinos más útiles y consumen recursos sin aportar valor.

Esto se ve mucho en bases de conocimiento donde múltiples artículos empiezan con la misma plantilla corporativa. “Estimado usuario, este documento describe el procedimiento para resolver incidencias relacionadas con…” Si generamos embeddings sobre todo el texto sin ponderar ni limpiar, la plantilla común puede contaminar la similitud. Después nos preguntamos por qué todos los documentos parecen iguales. Quizá porque hemos metido el mismo ruido en todos. Sorpresón.

Hay que decidir qué texto se embebe. No siempre interesa usar todo el documento. A veces conviene usar título, resumen, etiquetas, producto, síntomas y causa raíz. En otros casos interesa trocear documentos largos en fragmentos y buscar sobre chunks, no sobre documentos completos. Si un artículo de diez páginas contiene una sección relevante en la página ocho, el embedding global puede diluirla.

También hay que versionar el modelo de embeddings. Si hoy generamos vectores con un modelo y mañana con otro, no debemos mezclar ambos alegremente en la misma columna sin saber qué estamos haciendo. Las distancias entre vectores generados por modelos distintos pueden dejar de tener sentido. Como mínimo, guardaría ModeloEmbedding, FechaEmbedding, HashContenido y estado de generación.

Un diseño más serio podría separar documento y fragmentos:

Este modelo permite buscar fragmentos, no solo documentos. Después podemos agregar resultados por DocumentoID, aplicar RRF sobre fragmentos y mostrar el documento con el fragmento más relevante. Es más trabajo, claro. También es más útil. La alternativa es devolver documentos enormes y confiar en que el usuario encuentre el párrafo bueno. Eso no es buscador; es externalizar el problema.

Reordenación por documento cuando trabajamos con fragmentos

Cuando buscamos sobre fragmentos, aparece un problema adicional. Un documento largo puede tener muchos fragmentos y, por tanto, muchas oportunidades de aparecer. Si no controlamos esto, los documentos extensos dominan el ranking simplemente porque tienen más boletos. Es el mismo vicio de siempre, confundir cantidad con relevancia.

Una estrategia razonable consiste en fusionar primero a nivel de fragmento y después colapsar por documento, quedándonos con la mejor puntuación o aplicando una agregación controlada. Por ejemplo:

Aquí MAX(PuntuacionRRF) evita que un documento se dispare solo por tener muchos fragmentos moderadamente relevantes. El contador de fragmentos coincidentes puede servir como desempate suave, pero no debería dominar la ordenación. Si queremos algo más sofisticado, podemos usar una combinación entre mejor fragmento, número de fragmentos relevantes y diversidad de secciones.

En sistemas RAG, este patrón es especialmente útil. El buscador no solo decide qué documento mostrar; decide qué fragmento alimentar al modelo generativo. Si metemos fragmentos malos en el contexto, el modelo responderá con seguridad y poca vergüenza. La seguridad sin fundamento ya la conocemos de algunas reuniones de arquitectura, pero no hace falta automatizarla.

También podemos añadir filtros de seguridad y visibilidad antes de la búsqueda. Si un usuario no tiene permiso sobre un documento, ese documento no debe aparecer ni por semántica ni por full-text. Esto parece obvio hasta que alguien implementa el filtro después de generar el contexto para el modelo. 

Seguridad, permisos y fuga semántica

La búsqueda híbrida no solo afecta al rendimiento. También afecta a la seguridad. Un embedding puede codificar información sensible del texto original. No es una copia literal, pero tampoco deberíamos tratarlo como un dato inocente. Si generamos embeddings de documentos confidenciales y permitimos búsquedas semánticas sin aplicar permisos correctamente, podemos filtrar conocimiento aunque no devolvamos el texto completo.

La seguridad debe aplicarse antes de formar candidatos o durante el proceso de búsqueda, no al final de manera cosmética. En VECTOR_SEARCH, con índices recientes y filtrado iterativo, los predicados pueden aplicarse durante la búsqueda vectorial. Eso ayuda, pero no sustituye un diseño de seguridad correcto.

En SQL Server podemos resolver parte del problema con predicados relacionales, Row-Level Security o filtros explícitos por tenant, organización, rol o ámbito documental. Lo importante es que la rama léxica y la rama semántica usen exactamente la misma lógica de visibilidad. Si full-text filtra por permisos y vector no, la fusión RRF puede devolver identificadores que la rama textual jamás habría permitido. Y entonces empieza el baile.

También hay que controlar quién puede generar embeddings. Si usamos AI_GENERATE_EMBEDDINGS, debemos revisar modelo, permisos, configuración del servicio y trazabilidad. Si los generamos fuera y los cargamos en SQL Server, debemos validar dimensiones, formato, modelo, nulos y correspondencia con el texto original. Un embedding desalineado con su documento es una mentira muy rápida de consultar.

En entornos multi-tenant, yo evitaría búsquedas vectoriales globales salvo que el aislamiento esté muy claro. Filtrar por tenant no es opcional. Y si el volumen por tenant permite búsqueda exacta con VECTOR_DISTANCE, quizá esa sea la opción más controlable. No todo problema necesita ANN global. A veces la arquitectura sencilla gana porque tiene menos formas de fallar.

La seguridad no debe aparecer como epígrafe al final del proyecto. Si el buscador ya está construido y alguien pregunta “¿cómo filtramos por permisos?”, lo más probable es que la respuesta correcta sea “reescribiendo más de lo que te gustaría”.

Observabilidad: medir relevancia y medir coste

Una búsqueda híbrida tiene dos planos de observabilidad. El primero es técnico: duración, lecturas, CPU, memoria, spills, número de candidatos por rama, uso de índice vectorial, uso de índice full-text, plan elegido, cardinalidades estimadas y reales. El segundo es funcional: consultas reales, documentos mostrados, documentos abiertos, posición del clic, reformulaciones y satisfacción del usuario.

Si solo medimos rendimiento, podemos construir un buscador rapidísimo que devuelve basura. Si solo medimos relevancia, podemos construir un buscador magnífico que tumba producción cada vez que se usa. Ninguna de las dos cosas debería darnos orgullo profesional.

A nivel técnico, conviene registrar para cada búsqueda el texto normalizado, tamaño de candidatos léxico, tamaño de candidatos semántico, top_final, valor de k, pesos aplicados, duración total y duración por rama si separamos ejecución. También podemos guardar si la consulta se trató como “literal crítica” o “semántica dominante”. Esta trazabilidad permite ajustar sin adivinar.

A nivel funcional, el dato más valioso suele ser qué resultado acaba usando el usuario. En soporte, podemos vincular búsqueda con ticket resuelto o artículo seleccionado. Si es documentación interna, podemos medir apertura y permanencia. En buscadores de catálogo, conversión o selección. La relevancia real no siempre coincide con nuestra intuición, especialmente cuando los usuarios escriben cosas que harían llorar a un parser.

También hay que comparar full-text puro, vector puro e híbrido. No para hacer una gráfica bonita, sino para demostrar que la complejidad añadida aporta valor. Si la híbrida no mejora precisión, recall o tiempo de resolución, quizá estamos decorando arquitectura. Y la decoración en producción suele ser cara.

Un enfoque serio consiste en crear un conjunto de consultas de evaluación con resultados esperados. No tiene que ser perfecto. Basta con consultas representativas, incluyendo códigos exactos, frases ambiguas, sinónimos, errores ortográficos, consultas largas y consultas absurdamente cortas. Sobre ese conjunto podemos probar distintos pesos, tamaños de ventana y valores de k.

Sin evaluación, la búsqueda híbrida se convierte en “a mí me parece que va mejor”. Esa frase ha causado más daño que algunos NOLOCK.

Casos donde no usaría búsqueda híbrida

No todo buscador necesita híbrida. Si el usuario busca exclusivamente códigos, referencias exactas, identificadores legales o claves de producto, full-text o incluso índices relacionales bien diseñados pueden bastar. Meter embeddings ahí puede añadir ruido. Una búsqueda de Factura 2024-000834 no necesita poesía semántica.

Tampoco usaría híbrida como primera respuesta a un problema de contenido mal estructurado. Si los documentos no tienen títulos decentes, categorías, producto, idioma, fecha, permisos o estado, la búsqueda híbrida no arreglará el desastre. Solo lo hará más caro. Primero se ordena el contenido; después se mejora la recuperación.

En tablas pequeñas, evitaría crear índices vectoriales salvo que exista una razón clara. Microsoft indica que VECTOR_SEARCH puede funcionar sin índice mediante escaneo brute-force, aunque el rendimiento se degrada con datasets grandes. Para desarrollo, pruebas y conjuntos pequeños, esto puede ser suficiente.

También sería prudente con cargas masivas frecuentes. Si regeneramos embeddings continuamente, cambiamos modelos a menudo o reemplazamos gran parte del dataset, el coste de mantenimiento y reconstrucción puede pesar más que la ventaja de ANN. En esos casos hay que diseñar ventanas de carga, staging, versiones de índice y validaciones. No conviene descubrirlo durante una migración nocturna con tres personas mirando una barra de progreso.

Por último, no usaría híbrida sin una estrategia de seguridad clara. La búsqueda semántica puede recuperar contenido inesperado porque no depende de palabras exactas. Eso es precisamente lo que la hace potente. También es lo que la hace peligrosa si los permisos se aplican tarde o mal.

El mejor buscador no es el que usa más tecnologías. Es el que devuelve lo correcto, rápido, dentro de permisos y con un coste operativo razonable. Parece una obviedad, pero hay arquitecturas enteras construidas para demostrar lo contrario.

Buenas prácticas para llevarlo a producción

La primera buena práctica es separar claramente recuperación y reordenación. La rama full-text recupera candidatos léxicos. La rama vectorial recupera candidatos semánticos. RRF fusiona posiciones. Si mezclamos estas fases sin orden, acabaremos con consultas imposibles de depurar.

La segunda es limitar candidatos por rama. Ni full-text ni vector deberían devolver volúmenes masivos si el resultado final tendrá 20 filas. top_n_by_rank en CONTAINSTABLE y TOP (N) WITH APPROXIMATE en VECTOR_SEARCH son herramientas básicas para controlar el tamaño del problema.

La tercera es conservar filtros relacionales fuertes. Cliente, tenant, producto, estado, idioma, fecha y permisos deben formar parte del diseño. No todo debe resolverse por similitud. A veces una igualdad bien indexada vale más que un grafo vectorial entero. No queda tan bien en una presentación, pero ejecuta mejor.

La cuarta es versionar embeddings. Guarda modelo, fecha, hash del texto y estado de generación. Si cambias el texto, regenera el vector. Cuando cambies el modelo, no mezcles vectores sin control. Si falla la generación, no insertes basura silenciosa. La base de datos recordará tu negligencia con una precisión admirable.

La quinta es medir calidad, no solo latencia. Registra consultas reales y resultados usados. Ajusta pesos y ventanas con datos. RRF funciona muy bien como punto de partida, pero cada dominio tiene sus rarezas. En soporte técnico, los literales pesan mucho. Si es búsqueda documental, la semántica puede dominar. En comercio, atributos estructurados y disponibilidad pueden importar más que ambas.

La sexta es revisar versión y comportamiento de los índices vectoriales. Los índices antiguos tienen limitaciones distintas a los recientes. La sintaxis con TOP_N queda obsoleta para índices nuevos, mientras que SELECT TOP (N) WITH APPROXIMATE es el patrón actual documentado para la búsqueda aproximada con índices recientes.

La séptima es preparar despliegues y mantenimiento. Si usas DacPac, BACPAC, replicación, particionado o cargas masivas, revisa restricciones antes de comprometer la arquitectura. Los índices vectoriales no son invisibles para la operación diaria. El DBA va a convivir con ellos, y conviene que no sea en régimen de sorpresa permanente.

Todo esto suena menos emocionante que “hemos metido IA en el buscador”. También es bastante más probable que funcione.

Conclusión

La búsqueda híbrida no sustituye al full-text ni convierte la búsqueda vectorial en una solución universal. Lo interesante aparece precisamente al combinar ambas señales con criterio, full-text aporta precisión léxica, los vectores aportan contexto semántico y RRF permite fusionar resultados sin mezclar puntuaciones incompatibles.

El diseño correcto no consiste en lanzar dos consultas y rezar. Hay que limitar candidatos, aplicar filtros relacionales, elegir entre búsqueda exacta y aproximada, revisar versiones de índice, controlar permisos, medir relevancia y entender el coste operativo. La parte bonita de la demo dura diez minutos. La parte seria vive dia a dia en producción.

SQL Server está incorporando capacidades muy potentes para búsqueda semántica e híbrida, pero potencia no significa simplicidad. Un índice vectorial mal usado, una rama full-text sin límite o una fusión de puntuaciones mal planteada pueden convertir una gran idea en otra incidencia con prioridad alta.

La búsqueda híbrida merece sitio en arquitecturas modernas, especialmente en bases de conocimiento, soporte, documentación interna y sistemas RAG. Pero exige DBAs que entiendan el motor, no operadores que copien sintaxis de una demo. Como casi siempre, la diferencia entre una solución elegante y una chapuza cara está en los detalles. Y los detalles, por suerte o por desgracia, siguen sin indexarse solos.

 

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

¿Por qué no deberías usar SELECT * en vistas de SQL Server?

Usar SELECT * en una vista de SQL Server no es una buena práctica. Esa frase la hemos escuchado muchas veces y, en términos generales, es correcta. El problema aparece cuando la explicación se queda en la superficie y deja una idea equivocada: que una vista definida con SELECT * se comporta como si el asterisco se resolviera dinámicamente cada vez que consultamos la vista.

No funciona así. Y precisamente por eso el tema es más interesante de lo que parece.

Cuando creamos una vista con SELECT *, SQL Server no guarda una especie de comodín vivo que siempre representa “todas las columnas actuales de la tabla”. Durante la creación de la vista, el motor resuelve ese *, determina qué columnas existen en ese momento y almacena metadatos asociados a la vista. Es decir, aunque el texto de la definición pueda seguir mostrando SELECT *, la vista ya tiene una lista de columnas asociada internamente.

Ese matiz es importante porque cambia por completo la forma en la que debemos entender el riesgo. El problema no es solo que SELECT * lea más columnas de las necesarias. En una vista, además, puede generar inconsistencias entre lo que creemos que hemos definido, lo que existe en la tabla base y lo que realmente devuelve la vista.

¿Qué ocurre al crear una vista con SELECT * en SQL Server?

Vamos a partir de un ejemplo sencillo pero suficiente para ver el problema de cerca. Supongamos que creamos una tabla y después una vista usando SELECT *.

A simple vista parece que la vista representa todas las columnas de dbo.Clientes. Y en este momento concreto, eso es cierto. Si consultamos la vista, veremos ClienteID, Nombre y Email.

Pero SQL Server no interpreta ese * como una promesa dinámica de futuro. En el momento de crear la vista, el motor resuelve qué columnas forman parte de ella y guarda esa información en sus metadatos. Podemos comprobarlo consultando sys.columns.

Ahí veremos las columnas que SQL Server tiene asociadas a la vista. No estamos viendo directamente las columnas actuales de la tabla base, sino la metadata de la vista como objeto.

Esto es lo que muchas explicaciones simplifican demasiado. El SELECT * no queda “vivo” en el sentido que muchos imaginan. La vista tiene una estructura persistida. Y cuando la tabla base cambia, esa estructura no se actualiza automáticamente solo porque nos parezca lógico. SQL Server no está aquí para cumplir nuestras expectativas emocionales, por suerte para él.

SELECT * en vistas de SQL Server al añadir columnas

El primer caso es el más habitual: añadimos una columna nueva a la tabla base.

Mucha gente esperaría ver ahora cuatro columnas: ClienteID, Nombre, Email y Telefono. Pero no será así. La vista seguirá devolviendo las columnas que tenía asociadas cuando se creó. La nueva columna existe en la tabla, pero no aparece en la vista.

Aquí el problema es que no suele haber un error al ejecutar la vista. La consulta funciona. Y precisamente por eso el problema es peligroso: no rompe nada de forma evidente. Simplemente tenemos una discrepancia entre lo que parece decir la definición y lo que realmente devuelve el objeto.

Si miramos el texto de la vista, podemos seguir viendo algo como esto:

Pero si miramos las columnas de la vista en sys.columns, veremos que Telefono no forma parte de la metadata de la vista. La definición textual y la estructura efectiva del objeto ya no transmiten la misma idea. Magnífico, otra fuente de confusión silenciosa, como si no tuviéramos suficientes.

Para actualizar la metadata de una vista no enlazada a esquema podemos usar sp_refreshview.

Después de refrescar la vista, SQL Server vuelve a resolver la definición y actualiza sus metadatos. En ese momento la nueva columna puede aparecer. Pero eso no convierte SELECT * en una buena idea. Solo demuestra que la vista necesitaba una actualización explícita para alinear su metadata con la tabla base.

SELECT * en vistas de SQL Server al eliminar columnas

El segundo caso es más agresivo: eliminamos una columna que formaba parte de la vista cuando se creó.

Aquí el resultado puede ser un error al consultar la vista, porque la metadata de la vista sigue esperando una columna que ya no existe en la tabla base. Hemos cambiado el objeto del que depende la vista, pero la vista mantiene una definición interna que ya no puede resolverse correctamente.

Este punto es importante porque desmonta otra idea muy extendida: que las vistas nos protegen automáticamente de los cambios en las tablas base. No. Una vista es un objeto dependiente. Si cambiamos la tabla que hay debajo, podemos romper la vista, igual que podemos romper un procedimiento almacenado, una función o una consulta de aplicación. Realmente sería el mismo problema que si hubiéramos definido las columnas en el SELECT. 

La diferencia es que con SELECT * el problema puede quedar más escondido. Cuando escribimos las columnas explícitamente, vemos con claridad qué dependencias existen. Si una vista usa Email, sabemos que borrar Email rompe esa vista. Con SELECT *, esa dependencia queda disfrazada detrás de un comodín aparentemente inocente.

Y, como casi todo lo que aparenta inocencia en bases de datos, tarde o temprano acaba en una incidencia.

¿Por qué las columnas explícitas son mejores en una vista?

Cuando escribimos una vista así, el contrato queda claro:

Aquí no hay interpretación creativa. La vista devuelve esas columnas porque esas son las columnas que hemos decidido exponer. Si añadimos Telefono a la tabla base, la vista no lo devuelve. Pero eso ya no es una inconsistencia, es el comportamiento esperado. La vista representa un contrato concreto, no una puerta abierta a todo lo que aparezca en la tabla.

Si eliminamos Email, la vista fallará igualmente, pero el motivo será evidente. La definición dice que necesita Email y la columna ya no existe. No hay un * ocultando la dependencia real. El código expresa la intención.

Ese es el verdadero valor de listar columnas explícitamente. No se trata solo de rendimiento, aunque también. Se trata de legibilidad, mantenimiento, seguridad y estabilidad del contrato.

Una vista no debería ser “todo lo que haya en esta tabla”. Una vista debería representar una proyección concreta de datos con un propósito concreto. Si no sabemos qué columnas queremos devolver, probablemente tampoco sabemos muy bien para qué estamos creando la vista. Y en SQL Server eso suele acabar en una colección de objetos heredados que nadie se atreve a tocar, ese museo del terror que todas las empresas llaman “modelo de datos”.

SELECT * en vistas de SQL Server y seguridad

También hay un ángulo de seguridad que suele ignorarse. Si usamos vistas para exponer solo parte de la información de una tabla, SELECT * es especialmente mala idea.

Imaginemos que una tabla de clientes no contiene inicialmente información sensible. Creamos una vista con SELECT * y damos permisos sobre esa vista. Más adelante, alguien añade una columna con información que no debería exponerse a todos los usuarios.

Si la vista no se refresca, quizá esa columna no aparezca inmediatamente. Pero si en algún momento se ejecuta sp_refreshview, se altera la vista o se recrea, esa nueva columna podría acabar expuesta sin que nadie haya revisado conscientemente el contrato de seguridad.

Con columnas explícitas, este riesgo baja muchísimo. Añadir una columna nueva a la tabla no la expone automáticamente en la vista si esta se refresca. Para incluirla, alguien tiene que modificar la definición y escribirla. Ese pequeño acto de intención es importante. En seguridad, los cambios implícitos son una receta estupenda para que un lunes cualquiera empiece con reuniones desagradables.

SCHEMABINDING: cuando queremos bloquear el contrato

Si queremos ir un paso más allá, podemos crear la vista con WITH SCHEMABINDING.

Con SCHEMABINDING, SQL Server impide cambios en las tablas base que afecten a la vista mientras esa dependencia exista. No podremos eliminar una columna usada por la vista sin modificar antes la vista. Tampoco podremos usar SELECT *. Hay que escribir las columnas de forma explícita y usar nombres de objetos cualificados con esquema.

Esto no significa que todas las vistas deban llevar SCHEMABINDING. Depende del caso. Pero cuando una vista actúa como contrato estable para aplicaciones, informes, integraciones o seguridad, merece la pena considerarlo.

Además, las vistas indexadas requieren SCHEMABINDING, precisamente porque SQL Server necesita garantías fuertes sobre la estabilidad de la definición. No puede materializar resultados de una consulta cuyo esquema se mueve alegremente como si estuviera en una reunión de requisitos sin cerrar.

El verdadero problema de SELECT * en vistas de SQL Server

La recomendación “no uses SELECT *” es correcta, pero se queda corta si no explicamos el mecanismo interno.

En consultas ad hoc, SELECT * ya tiene problemas conocidos: lee columnas innecesarias, aumenta tráfico de red, puede empeorar operaciones intermedias y acopla el código al esquema completo de la tabla. Pero en vistas añadimos otro problema: la metadata persistida de la vista puede quedar desalineada respecto a la tabla base.

Al añadir columnas, la vista puede no reflejar los cambios. Al eliminar columnas, puede fallar. Y en todos los casos el origen del problema es el mismo: estamos usando un comodín en un objeto que debería tener un contrato estable.

Por eso la buena práctica no debería formularse solo como “usa columnas explícitas porque es más mantenible”. Deberíamos decir algo más preciso: en una vista, SELECT * se resuelve en el momento de crear o refrescar el objeto, no cada vez que lo consultamos como muchos esperan. Esa diferencia explica los errores, las inconsistencias y los sustos.

Conclusión

SELECT * en una vista de SQL Server no es simplemente una mala costumbre estética. Es una forma de esconder dependencias, debilitar el contrato de la vista y crear una falsa sensación de dinamismo que no existe.

Cuando creamos una vista, SQL Server almacena metadata sobre sus columnas. Si después cambia la tabla base, esa metadata puede quedar desactualizada. Añadir columnas puede no reflejarse en la vista. Eliminar columnas puede romperla.

La solución razonable es escribir siempre las columnas explícitamente, entender la vista como un contrato y usar SCHEMABINDING cuando necesitemos proteger ese contrato frente a cambios accidentales. No se trata de obedecer una regla de buenas prácticas porque sí. Se trata de entender cómo SQL Server interpreta realmente nuestra definición.

Y ahí está la diferencia entre repetir “no uses SELECT *” como un mantra y saber explicar por qué, que casualmente es donde empiezan las buenas decisiones técnicas.

 

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