Roberto Carrancio

Mi nombre es Roberto Carrancio y soy un DBA de SQL server con más de 10 años de experiencia en el sector. Soy el creador del blog soydba.es donde intento publicar varios artículos a la semana (de lunes a viernes que los fines de semana me gusta estar con mi gente y disfrutar de mi moto) Espero que disfrutes leyendo este blog tanto como yo disfruto escribiendo y que te sea de utilidad. Si tienes alguna sugerencia, pregunta o comentario, puedes dejarlo al final de cada entrada o enviarme un correo electrónico. Estaré encantado de leerte y responderte. ¡Gracias por tu visita! Mi principal interés es compartir mi conocimiento sobre bases de datos con todo el que quiera aprenderlo. Me parece un mundo tan apasionante como desconocido. Fuera de lo profesional me encanta la cocina, la moto y disfrutar de tomar una cervecita con amigos.
Mi nombre es Roberto Carrancio y soy un DBA de SQL server con más de 10 años de experiencia en el sector. Soy el creador del blog soydba.es donde intento publicar varios artículos a la semana (de lunes a viernes que los fines de semana me gusta estar con mi gente y disfrutar de mi moto) Espero que disfrutes leyendo este blog tanto como yo disfruto escribiendo y que te sea de utilidad. Si tienes alguna sugerencia, pregunta o comentario, puedes dejarlo al final de cada entrada o enviarme un correo electrónico. Estaré encantado de leerte y responderte. ¡Gracias por tu visita! Mi principal interés es compartir mi conocimiento sobre bases de datos con todo el que quiera aprenderlo. Me parece un mundo tan apasionante como desconocido. Fuera de lo profesional me encanta la cocina, la moto y disfrutar de tomar una cervecita con amigos.

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

¿Qué pasa cuando ejecutamos una consulta en SQL Server?

Para ojos inexpertos puede parecer trivial. Escribimos una SELECT, pulsamos Ejecutar y SQL Server nos devuelve filas, o al menos devuelve un error con una explicación bastante agresiva de por qué no puede ejecutar la consulta. Esa visión sirve para un usuario primerizo, pero se queda ridículamente corta en cuanto queremos entender rendimiento, bloqueos, estimaciones o por qué dos consultas casi idénticas terminan con comportamientos opuestos.

La realidad es menos mágica y mucho más de ingeniería de esa que nos gusta en este blog. Cuando ejecutamos una consulta, el motor no “lee tablas” sin más. Interpreta el lote, valida sintaxis y semántica, resuelve objetos y permisos, decide si reutiliza un plan o compila uno nuevo, estima cardinalidades, elige operadores físicos, reserva memoria, accede a páginas en memoria o disco, coordina concurrencia y devuelve resultados al cliente. Y todo eso ocurre en milisegundos cuando las cosas van bien. Cuando van mal, nos regala una tarde entera de entretenimiento buscando un problema que no vemos.

¿Parece complicado verdad? Pues espera que te lo explico con más detalle. Pero antes conviene fijar una idea: SQL es declarativo. Nosotros describimos el resultado que queremos, no la secuencia exacta de pasos para obtenerlo. Esa diferencia, que parece un detalle académico, es justo la que separa una explicación decente de un mito de pasillo.

El lote, la sesión y el contexto

Lo primero que recibe SQL Server no es una consulta aislada en una urna de cristal. Recibe un lote (batch) T-SQL enviado por una aplicación cliente, normalmente SSMS, un programa, un driver ODBC/OLE DB o cualquier otro consumidor que hable TDS. Ese lote viaja con un contexto de sesión que importa más de lo que muchos quisieran admitir. Incluye, entre otras cosas, la base de datos actual, el usuario, configuraciones regionales y de idioma, opciones SET, nivel de aislamiento, estado de la transacción y otros detalles que suelen parecer inocentes hasta que rompen la reutilización de planes.

Eso significa que el mismo texto no siempre equivale a la misma ejecución. Dos sesiones con distintas opciones SET pueden acabar con planes diferentes. Dos usuarios con permisos distintos pueden provocar resoluciones distintas. Incluso el contexto transaccional puede condicionar bloqueos, versionado y comportamiento del optimizador. Antes de tocar una sola página de datos, SQL Server ya está operando dentro de unas condiciones concretas. Esas condiciones cuentan luego cuando alguien jura que “la query es la misma” pero no va igual.

Antes de hablar de planes y operadores conviene pasar por la primera puerta del proceso. No porque sea la más compleja, sino porque mucha gente confunde “la consulta compila” con “la consulta está bien”. Son cosas muy distintas, por desgracia.

Parsear no es ejecutar

Una vez que tenemos el lote, la primera fase interna sería el parsing. El motor tokeniza el texto, valida la sintaxis y construye una representación inicial de la consulta. Aquí se detectan errores de sintaxis de esos que todos cometemos cuando estamos cansados: paréntesis mal cerrados, comas absurdas, palabras clave fuera de sitio o expresiones que no encajan en el lenguaje. Superar esta fase solo significa que escribimos algo comprensible para el parser. No significa que tenga sentido desde el punto de vista del motor ni, desde luego, que sea una buena idea.

Después entra el algebrizer, a veces llamado binder. Aquí SQL Server resuelve el significado real de lo que hemos escrito. Tiene que averiguar a qué tabla pertenece cada columna, expandir el *, validar alias, comprobar agregaciones, deducir tipos de datos, aplicar conversiones implícitas y verificar permisos. Es la fase en la que dejamos de pensar en cosas estéticas y empiezan los errores con sustancia: columnas inexistentes, referencias ambiguas, funciones mal usadas, objetos inaccesibles o expresiones incompatibles. También en este punto se van a comprobar los permisos del usuario sobre todos los objetos necesarios. 

Además, el motor construye una representación lógica de la consulta que ya no es puro texto, sino álgebra relacional. En otras palabras, transforma la sentencia en operaciones como proyección, selección, join o agregación. Todavía no ha decidido cómo ejecutar nada físicamente, pero ya sabe qué significa cada pieza y qué resultado lógico perseguimos. También aquí puede aparecer la parametrización automática, esa ayuda que a veces mejora la reutilización de planes y otras veces solo prepara el terreno para un precioso caso de parameter sniffing. La estabilidad nunca ha sido un valor central en la vida del DBA.

A partir de aquí llega una bifurcación clave. Si ya existe un plan reutilizable, SQL Server puede ahorrar mucho trabajo. Si no, toca compilar. Y ahí empieza la parte que realmente decide si una consulta va a tardar milisegundos o va a convertir TempDB en una zona de conflicto.

Caché de planes, optimización y estimaciones

Con la consulta ya entendida, SQL Server mira la caché de planes. Para esto, se usa un hash del texto de la consulta. Si encuentra un plan válido para ese hash y ese contexto, puede reutilizarlo. Esto es fundamental porque compilar cuesta CPU y tiempo. En cargas OLTP, donde una misma consulta se ejecuta miles de veces, la reutilización es parte del rendimiento.

Si no hay plan reutilizable, o si es necesaria una recompilación por cambios en estadísticas, metadatos o condiciones de ejecución, entra el optimizador. Aquí conviene matar un mito viejo: el optimizador no busca el plan perfecto, porque eso exigiría explorar demasiadas combinaciones. Busca un plan suficientemente bueno según su modelo de costes y dentro de un presupuesto finito de tiempo. Lo bastante listo para rendir, lo bastante práctico para no pasarse más tiempo pensando que ejecutando.

Para tomar decisiones usa estadísticas, histogramas, densidades, índices disponibles, restricciones, cardinalidades estimadas y reglas de transformación. Puede reordenar joins, empujar predicados, simplificar expresiones, eliminar operaciones redundantes y elegir entre Nested Loops, Hash Match o Merge Join, entre acceso por seek, scan o lookup, entre ejecución serie o paralela, y entre distintos tamaños de memory grant. Gran parte del rendimiento nace aquí, mucho antes de la primera lectura física.

El mito del orden lógico

Aquí encaja una distinción importante que suele explicarse mal. El famoso orden lógico de una consulta, ese FROM/JOIN, WHERE, GROUP BY, HAVING, SELECT, ORDER BY, sirve para entender el significado del lenguaje. Es útil para razonar sobre el resultado, no para describir el recorrido físico real. SQL Server no ejecuta la consulta siguiendo ese diagrama como quien monta un mueble sueco de esa famosa casa de muebles que estás pensando. El motor puede empujar filtros hacia abajo, reordenar joins y empezar por el conjunto más selectivo si las estimaciones y los índices lo hacen recomendable.

Eso significa que en una consulta entre Clientes y Pedidos, con un filtro muy selectivo sobre Clientes, el plan puede localizar primero esos pocos clientes con un Index Seek y solo después unir con Pedidos. Lógicamente seguimos teniendo un JOIN y un WHERE, pero físicamente el motor ha reorganizado el trabajo para reducir filas cuanto antes. No es una contradicción. Es exactamente lo que esperamos de un optimizador de consultas. Confundir orden lógico con plan físico es una de esas costumbres que sobreviven porque caben bien en una infografía simplona hecha con IA por un gurú de LinkedIn (no del trabajo real).

El problema, claro, es que el optimizador decide en función de estimaciones. Si estima bien, suele elegir razonablemente bien. Si estima mal, el plan puede ser impecable en apariencia y terrible en producción. Un Nested Loops sobre millones de filas, un memory grant insuficiente que termina en spill a TempDB o un paralelismo absurdo no suelen venir de la maldad del motor, sino de una mala predicción. Las estadísticas no son decoración. Son una de las pocas cosas que separan la ingeniería de la adivinación y las apuestas.

Una vez elegido el plan, empieza la ejecución de verdad. Y aquí también conviene aparcar otra simplificación: el plan no se ejecuta como solemos leer el dibujo en SSMS.

Del plan al motor de ejecución

Siempre te han dicho que los planes de ejecucion se leen de derecha a izquierda de abajo a arriba. Y es verdad hasta cierto punto. El motor de ejecución trabaja con un modelo de iteradores, normalmente en modo pull. Un operador superior pide una fila al operador inferior, que a su vez la pide al siguiente, y así sucesivamente. Ese “dame la siguiente fila” parece una trivialidad, pero explica muchas cosas. Explica por qué algunos operadores pueden empezar a devolver resultados casi de inmediato y otros bloquean hasta haber consumido buena parte de la entrada.

Un Top puede cortar el trabajo muy pronto. Un Sort necesita reunir todos los datos y ordenar antes de darle nada al siguiente operador. Un Hash Match puede requerir construir primero una tabla hash en memoria. Un agregado puede operar en streaming o bloquear, según el operador concreto y el plan. Y así con muchos operadores. Además, desde SQL Server 2012 en adelante y con más presencia en versiones modernas, algunas consultas pueden ejecutarse en modo batch, no fila a fila, con beneficios importantes en CPU y throughput. No todo circula del mismo modo por el plan, y esa diferencia se nota.

También aquí aparecen efectos derivados del memory grant. Si el optimizador concede memoria suficiente, un Sort o un Hash Match viven tranquilos. Si se queda corto, llegan los spills a TempDB y el plan empieza a pagar intereses. Y TempDB, como sabemos, siempre está encantada de recibir problemas ajenos en horario laboral.

Hasta ahora hemos hablado del motor relacional. Pero las filas no salen de una abstracción platónica. Alguien tiene que ir a buscarlas. Ahí entra el storage engine, que es donde las cosas pueden torcerse.

Buffer pool, páginas e I/O real

Cuando el plan necesita datos, el storage engine localiza las páginas necesarias. Si esas páginas ya están en memoria, dentro del buffer pool, puede trabajar con ellas inmediatamente. Si no lo están, debe solicitarlas al subsistema de almacenamiento para cargarlas en el buffer pool y, a partir de ahí, continuar la ejecución sobre esas páginas ya residentes en memoria. Esto es clave, el motor no va a pasar datos del disco duro a los resultados de tu consulta, siempre pasan por el buffer pool de la memoria RAM. Y ahí aparecen los costes reales de I/O, los tiempos de espera y toda la artillería interna que rara vez se explican bien, navegación por B-trees, páginas hoja, IAMs, read-ahead, latches y comprobaciones varias.

Un Index Seek no significa “esto será rápido” por definición. Significa que el motor puede navegar por la estructura del índice hasta un rango concreto. Si luego necesita hacer miles de lookups para recuperar columnas no cubiertas por el índice, el coste puede dispararse. Del mismo modo, un Scan no siempre es malo. A veces es la opción correcta si el volumen a leer es grande o si la selectividad no compensa una ruta aparentemente más elegante. El problema no es el nombre del operador, sino el contexto, las filas reales y el trabajo asociado.

La presencia o ausencia de datos en memoria también cambia por completo la película. Una consulta sobre páginas calientes puede correr como un tiro y la misma, minutos después, tener que pelear con lecturas físicas porque la caché cambió. Por eso las pruebas “me ha ido rápido una vez en mi portátil” tienen el valor técnico que suelen tener, muy poco.

Si la consulta solo leyera datos ya tendríamos bastante material, pero muchas veces también modifica. Y cuando modifica, el motor deja de ser solo lector y pasa a garantizar consistencia, aislamiento y durabilidad. Es decir, empieza la parte donde un UPDATE inocente se convierte en un pequeño proyecto de infraestructura.

Bloqueos, versionado y transaction log

Durante la ejecución, SQL Server coordina concurrencia. Adquiere bloqueos compartidos, exclusivos o de actualización, puede escalar bloqueos si la presión lo justifica y, si la base trabaja con READ_COMMITTED_SNAPSHOT o SNAPSHOT, puede recurrir al versionado de filas. Eso significa que una consulta puede tardar por lo que calcula, pero también por lo que espera. Esperas por locks, latches, I/O, memoria o CPU convierten muchas consultas lentas en consultas bloqueadas, contenidas o asfixiadas por el entorno.

Cuando hay escrituras, además, entra en juego el transaction log. Un INSERT, un UPDATE o un DELETE no consisten en cambiar valores y seguir adelante. Hay que registrar la operación, mantener índices, validar restricciones, comprobar claves externas, ejecutar triggers si existen y asegurar propiedades ACID. El COMMIT no es un adorno en estos casos. Es el momento en el que la durabilidad exige que el log esté donde tiene que estar. Si el subsistema de almacenamiento del log no acompaña, lo notaremos muy deprisa.

Por eso dos operaciones aparentemente modestas pueden tener costes muy distintos. Una actualización sobre una tabla con varios índices, triggers y claves foráneas puede mover bastante más trabajo interno del que sugiere una línea de T-SQL. Y no, “solo eran 500 filas” no impresiona a nadie cuando cada fila arrastra media docena de tablas referenciadas.

Todo esto nos lleva a la pregunta inevitable. Si la consulta es la misma, ¿por qué unas veces rinde bien y otras no? La respuesta, para fastidio de quienes esperan una causa única y limpia, es que la ejecución nunca depende solo del texto.

¿Por qué la misma query no siempre corre igual?

La misma consulta puede reutilizar un plan excelente o uno desastroso. Puede encontrar datos en memoria o tener que leerlos de disco, ejecutarse con poca concurrencia o dentro de una tormenta de bloqueos, recibir una estimación razonable o una completamente deformada por estadísticas antiguas, parámetros sesgados o distribuciones raras. También puede obtener un memory grant suficiente o terminar desbordando a TempDB desde el minuto uno.

Por eso el análisis serio siempre se apoya en evidencias: plan real, filas estimadas frente a reales, lecturas lógicas, tiempo de CPU, waits, uso de memoria, spills, recompilaciones y contexto de ejecución. Como ya hemos comentado otras veces al hablar de planes de ejecución, estadísticas o parameter sniffing, el texto T-SQL es solo una parte del problema. Importa, claro. Pero el comportamiento real nace de la combinación entre consulta, datos, estado del servidor y decisiones del optimizador.

Cuando entendemos ese recorrido dejamos de hablar de “consultas lentas” como si fueran criaturas con voluntad propia. Empezamos a hablar de ejecuciones concretas, planes concretos y cuellos de botella concretos. Y a partir de ahí ya se puede trabajar de verdad, que siempre resulta menos cómodo que opinar, pero da mejores resultados.

Conclusión

Cuando ejecutamos una consulta en SQL Server no ocurre magia. Ocurre una cadena compleja de análisis, resolución, optimización, acceso a datos, control de concurrencia y devolución de resultados. El motor no sigue el orden visual con el que leemos la sentencia, ni toma decisiones por estética, ni premia nuestras buenas intenciones. Hace estimaciones, aplica reglas, reutiliza lo que puede y ejecuta bajo las condiciones reales del sistema.

Entender ese proceso cambia por completo la forma de diagnosticar rendimiento. Dejamos de mirar solo la consulta y empezamos a mirar el plan, las estadísticas, la memoria, el log, los bloqueos y las esperas. Ahí es donde se separa la teoría útil de una infografía bonita. Y también donde empieza el trabajo del DBA de verdad.

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

Publicado por Roberto Carrancio en Cloud, Rendimiento, SQL Server, 0 comentarios
GitHub Copilot con SQL Server: el choque entre SSMS y VS Code

GitHub Copilot con SQL Server: el choque entre SSMS y VS Code

El pasado mes de marzo, con la SQLCon 2026 de Atlanta, vimos varias novedades alrededor de GitHub Copilot para quienes trabajamos con SQL Server. Microsoft aprovechó el evento para reforzar dos frentes que, desde fuera, parecen muy parecidos: SSMS 22, ya con Copilot en disponibilidad general, y la extensión MSSQL de VS Code, que sigue creciendo con diseñador de esquemas, agent mode y otras superficies más cercanas al flujo de desarrollo. La mayoría de titulares se quedaron en la parte fácil, esa de “Copilot llega a todas partes”. Lo interesante empieza cuando uno rasca un poco y descubre que no, que no está llegando igual a todas partes, ni con la misma idea detrás.

En este artículo quiero centrarme en dos mecanismos concretos, porque ahí está la diferencia de verdad: las instrucciones personalizadas y las skills. Son dos formas distintas de decirle a Copilot cómo queremos que piense, qué contexto debe respetar y hasta dónde puede dejar de improvisar. Que ya sabemos que improvisar es una habilidad preciosa en jazz, pero en bases de datos suele costar bastante más.

Antes de entrar en el detalle, conviene empezar por lo que debería ser la base de cualquier uso serio de Copilot con SQL Server: las instrucciones.

Instrucciones personalizadas: donde de verdad empieza el contexto

Cuando hablo de instrucciones personalizadas no me refiero a un prompt largo y dramático que uno pega en el chat cada lunes. Me refiero a contexto persistente, reutilizable y con cierta autoridad. En VS Code, la documentación de Copilot lo plantea como un mecanismo para definir reglas comunes en archivos Markdown y hacer que se apliquen automáticamente a las peticiones de chat del workspace o del repositorio. En SSMS existe también ese modelo clásico de instrucciones de usuario y de repositorio, con copilot-instructions.md a nivel personal y .github/copilot-instructions.md a nivel de repositorio. Hasta aquí, ambos mundos hablan un idioma bastante parecido.

Un ejemplo simple de ese tipo de fichero sería algo así:

Este tipo de instrucciones funciona muy bien para marcar estilo, guardarraíles y convenciones. En otras palabras, sirve para decirle a Copilot cómo queremos que se comporte. Y eso ya ayuda bastante, sobre todo en equipos donde todavía hay quien cree que una tabla llamada Tbl_Clientes2_Final transmite serenidad arquitectónica. En VS Code, además, estas instrucciones pueden generarse con /init o /create-instructions, se aplican automáticamente al chat y tienen una prioridad definida entre nivel personal, repositorio y organización.

Instrucciones personalizadas adaptadas a SQL Server

Ahora bien, SSMS añade una capa bastante más interesante, y aquí es donde esto deja de ser genérico. Además de las instrucciones de usuario y repositorio, SSMS incorpora database instructions, que viven dentro de la propia base de datos como metadatos basados en propiedades extendidas y que aplican para todo el que consulte la base de datos con SSMS. Microsoft documenta dos nombres concretos: CONSTITUTION.md, para reglas globales de la base, y AGENTS.md, para contexto específico de objetos. No estamos hablando ya de “me gusta que indentes así”. Estamos hablando de “esta base funciona así, estas reglas de negocio no son negociables y esta tabla significa esto, aunque por su nombre nadie lo diría”.

CONSTITUTION.md es, en la práctica, una constitución de base de datos. Sirve para fijar normas generales y semánticas que deben aplicarse a cualquier interacción de Copilot con esa base. Un ejemplo razonable podría ser este:

Ese fichero no mejora el estilo. Mejora el entendimiento. Y esa diferencia es crucial. Cuando Copilot genera T-SQL sin contexto semántico, lo único que hace es adivinar con bastante seguridad en sí mismo. Cuando trabaja con una constitución pegada a la base, al menos tiene una oportunidad real de no inventarse la lógica del negocio sobre la marcha. Que parece una exigencia modesta, pero viendo algunas demos de IA últimamente casi suena revolucionaria.

AGENTS.md, por su parte, baja un nivel más y se aplica a objetos concretos. Ahí es donde tiene sentido documentar lo que jamás se deduce mirando nombres de columnas. Por ejemplo:

Este ejemplo es simple, pero deja ver lo importante: en SSMS el contexto puede residir dentro del dato y no solo alrededor del dato. Para mí, esa es la mejor idea que ha aparecido en esta historia. No porque sea vistosa, sino precisamente porque no lo es. Es una solución pensada por alguien que ha entendido que en SQL Server el problema rara vez es escribir una consulta; el problema es saber qué consulta tiene sentido escribir.

Una vez aclarado esto, toca la segunda pieza del artículo. Porque si las instrucciones sirven para fijar normas, las skills juegan una partida distinta.

Skills: capacidad reutilizable, no autoridad semántica

Las skills, tal como las documenta VS Code, no son instrucciones generales, sino capacidades especializadas. Hay que decir que las Skills no son exclusivas de Copilot, son un estandar de la industria presente en la mayoría de IAs generativas comerciales (Claude, OpenAI, Gemini o Github Copilot, por ejemplo). Sin embargo, a fecha de redaccion de este artículo, en SSMS no las tenemos disponibles. 

Sobre su uso, la propia documentación de Agent Skills las contrapone a las custom instructions de forma bastante clara: las instrucciones sirven para definir estándares y guías; las skills sirven para enseñar flujos, incluir scripts, ejemplos y recursos, y cargarse bajo demanda cuando el agente las considera relevantes. También son portables entre VS Code, Copilot CLI y otros entornos compatibles, mientras que las custom instructions son más específicas del ecosistema de VS Code y GitHub.

Dicho sin envoltorio comercial, una instrucción le dice a Copilot “compórtate así”; una skill le dice “cuando toque esto, trabaja de esta manera”. Es una diferencia importante, porque mucha gente intenta usar las skills como si fueran una constitución universal y luego se lleva la sorpresa correspondiente. No están pensadas para eso. Están pensadas para encapsular un procedimiento. La herramienta es buena. El malentendido también.

Un ejemplo sencillo de skill para SQL podría ser este:

Esto tiene bastante sentido en VS Code. Puede servir para reforzar disciplina antes de tocar esquema, para revisar convenciones o para encadenar pequeñas validaciones. Incluso puede ser muy útil en un equipo de desarrollo que trabaja con SQL y .NET en el mismo workspace. Pero conviene no pedirle a una skill lo que no promete. Una skill no reemplaza una constitución de base de datos, porque no vive en la base ni tiene ese nivel de anclaje semántico. Puede ayudar a leer instrucciones, empujar un flujo y mejorar bastante el resultado. Lo que no hace es convertir el contexto del repositorio en contexto nativo del dato.

Además, la propia documentación es bastante explícita con el alcance: las custom instructions se aplican siempre o por patrón; las skills se cargan para tareas específicas y bajo demanda. Esa diferencia basta para entender por qué una skill no es el sitio correcto para definir autoridad continua. Sirve para procedimiento. No sirve como sustituto serio de una política persistente. Y eso, en entornos con varias bases, varios dominios y objetos sensibles, importa bastante más de lo que algunos quieren admitir.

Llegados aquí, la pregunta lógica sale sola. Si las instrucciones personalizadas son tan útiles y las skills aportan tanto en VS Code, ¿por qué no tenemos exactamente lo mismo en ambos sitios?

¿Por qué no están en ambos sitios?

Como acabamos de ver en SSMS tenemos las database instructions y en VS Code las Skills pero no al revés. ¿Por qué?

La respuesta corta es que SSMS y VS Code no están diseñados para obedecer al mismo centro de gravedad y por tanto llevan caminos de desarrollo distintos. SSMS parte de la conexión activa, del esquema y de la base de datos como lugar natural del contexto. La documentación de Copilot en SSMS insiste precisamente en esa idea, entiende la conexión, el esquema y el trabajo que estamos haciendo sobre la base. Por eso tiene sentido que Microsoft haya añadido database instructions y que las haya apoyado en metadatos dentro de SQL Server. Si el centro es la base, el contexto tiene que poder vivir ahí.

VS Code, en cambio, está pensado para algo más amplio. La documentación de la extensión MSSQL lo describe como una experiencia para desarrolladores modernos, arquitectos, ingenieros de base de datos y escenarios donde SQL convive con ORM, APIs, generación de código, query builder, schema designer y agent mode. Incluso el diseñador de esquemas con Copilot abre una sesión de chat “scoped to the current schema context”, con cambios reflejados en diagrama, T-SQL y diff. Todo eso encaja muy bien con un entorno cuyo centro no es la base aislada, sino el proyecto completo.

Y aquí aparece la diferencia que, a mi juicio, merece un palito para las cabezas pensantes de Redmon. Si un desarrollador usa VS Code para todo, desde SQL hasta .NET, puede centralizar reglas del equipo en el repositorio y montar skills para reforzar flujos. Muy bien. Pero no puede apoyarse en un mecanismo nativo equivalente al de SSMS para adaptar contexto por base de datos o por tabla desde dentro del propio SQL Server. Tiene que definirlo todo en el mismo sitio o trocearlo artificialmente por carpetas, workspaces y archivos. Funciona, sí. Pero obliga a administrar el conocimiento desde fuera de donde ese conocimiento realmente vive. Y eso, en bases de datos con semánticas distintas, no es una limitación teórica. Es una limitación bastante práctica.

En otras palabras, SSMS ha resuelto mejor la pregunta “¿dónde debería vivir el contexto de una base de datos?”. VS Code ha resuelto mejor la pregunta “¿cómo integro Copilot en un flujo de desarrollo más amplio?”. Las dos respuestas tienen sentido. Lo que no tiene sentido es fingir que son intercambiables. Porque no lo son. Y cuando uno intenta usarlas como si lo fueran, acaba pidiéndole a una skill que haga de constitución o a un fichero de repositorio que actúe como conocimiento de tabla. 

Ya con esto, la conclusión debería ser simple. Y como el tema da para pedir más de lo que hay hoy, la cierro con una pequeña lista de deseos.

Conclusión: lo que me gustaría ver a continuación

Mi sensación a día de hoy es bastante clara. Las instrucciones personalizadas de base de datos son el mecanismo correcto para fijar normas persistentes. Las skills son el mecanismo correcto para empaquetar capacidades y flujos reutilizables. El problema no está en ninguna de las dos ideas. El problema está en que cada producto ha desarrollado solo la mitad que más le convenía. SSMS ha entendido muy bien el valor del contexto dentro de la base, pero no ofrece el ecosistema de capacidades componibles que sí vemos en VS Code. VS Code, por su parte, tiene una historia mucho más rica en skills, agentes y superficies de desarrollo, pero sigue sin bajar el contexto al nivel de la base y del objeto con la naturalidad que sí ofrece SSMS.

Mi primera petición sería bastante obvia: que VS Code pudiera consumir de forma nativa una constitución y agentes definidos dentro de SQL Server, no solo desde el repositorio. La segunda, que el diseñador de esquemas respetara desde el primer minuto ese contexto y no solo el esquema cargado. La tercera, que SSMS heredara una idea de skills más seria para procedimientos repetibles y no se quedara únicamente en la parte de instrucciones. Y la cuarta, quizá la más importante, que Microsoft dejara de presentar todo esto como si fuera el mismo Copilot en dos ventanas distintas, porque no lo es y ya va siendo hora de dejar de tratar a los usuarios técnicos como si no supieran distinguir entre contexto, estilo y gobierno.

Mientras eso llega, yo lo resumiría así: si quiero que Copilot respete mejor el dato, hoy miro a SSMS; si quiero que Copilot me acompañe mejor en el proyecto, hoy miro a VS Code. Y entre una cosa y otra, lo que seguimos necesitando es exactamente lo mismo que antes de la IA: contexto bueno, reglas claras y menos fe ciega en herramientas que todavía confunden velocidad con criterio.

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

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

SQL Database en Fabric: cuánto tiene de SQL y cuánto de Fabric

Con SQL Database en Fabric Microsoft hizo algo realmente inteligente, coger un motor que conocemos bien, Azure SQL Database, y colocarlo dentro del perímetro operativo de Fabric para vender una historia unificada de datos, analítica y desarrollo. La pregunta útil no es si “es SQL”, porque sí lo es. La pregunta útil es otra: cuánto de Azure SQL conserva, cuánto sacrifica por vivir en Fabric y en qué escenarios compensa ese intercambio. El servicio está en disponibilidad general desde noviembre de 2025 y Microsoft lo presenta como una base de datos SQL serverless y “SaaS-native” dentro de Fabric.

Antes de entrar en límites y promesas, conviene despejar el malentendido principal.

¿Qué significa realmente que sea una Azure SQL Database en Fabric?

Cuando Microsoft dice que SQL Database en Fabric está “basada en Azure SQL Database”, no está jugando con las palabras. Usa el mismo SQL Database Engine que Azure SQL Database, expone TDS, se puede atacar con SSMS, VS Code, sqlcmd o bcp, y mantiene una experiencia T-SQL muy reconocible para cualquiera que lleve años peleándose con SQL Server y su descendencia cloud. Hasta aquí, bien, no es un juguete con icono nuevo y marketing con esteroides.

Ahora viene la parte que importa. No es “Azure SQL Database metida dentro de un workspace” y listo. El contrato de servicio cambia. El licenciamiento cambia. El modelo de consumo cambia. La red cambia. La seguridad cambia. Y varias capacidades de plataforma que en Azure SQL ya damos por sentadas aquí no están, o han llegado más tarde, o llegan con otra forma. Dicho de otro modo, el motor sí, el producto no del todo. Y en arquitectura de datos, como ya sabemos, el diablo se esconde en la letra pequeña

Aquí está la clave técnica que explica casi toda la propuesta.

Arquitectura: no es un único almacén, son dos representaciones coordinadas

SQL Database en Fabric guarda sus datos operacionales en ficheros .mdf, igual que Azure SQL Database, pero además replica automáticamente los datos a OneLake en formato Delta Parquet. Sobre esa copia se crea un SQL analytics endpoint de solo lectura, accesible por TDS, para lanzar consultas analíticas sin castigar el workload transaccional. El espejo arranca al crear la base de datos, no requiere configuración manual y, en su comportamiento base, siempre ha estado activado. Técnicamente, por tanto, no hablamos de “el mismo dato en el mismo motor para todo”, sino de una base operacional más una representación analítica mantenida casi en tiempo real dentro de la misma plataforma.

Eso hace que el nuevo mensaje de “translítico” que nos quieren vender tenga su parte de verdad pero también una gran parte de jerga comercial. Sí, los datos operativos quedan disponibles casi al momento para analítica, Spark, notebooks, Power BI y otras piezas de Fabric. Pero no porque el OLTP y la analítica compartan mágicamente el mismo camino de ejecución, sino porque Fabric automatiza el reflejo a OneLake y pone un endpoint analítico encima. El resultado práctico puede ser muy bueno. La simplificación conceptual que a veces se cuenta, no tanto.

Una vez entendido eso, ya podemos responder la pregunta incómoda de verdad.

Anatomía interna: plano de control, plano de datos y replicación

Si queremos entender de verdad qué es SQL Database en Fabric, no basta con decir que “es Azure SQL dentro de Fabric”. Hay que separar claramente el plano de control del plano de datos. Y aquí está una de las claves del producto. 

La experiencia de creación, gobierno, acceso y operación vive en Fabric, a través de su control plane y su UX. Pero la base de datos operacional sigue ejecutándose sobre infraestructura de Azure SQL, con almacenamiento propio de datos, log y copias de seguridad, y con un formato de almacenamiento transaccional clásico orientado a rowstore. Es decir, el envoltorio operativo es Fabric, pero el corazón que procesa las transacciones sigue siendo Azure SQL Database.

Ese matiz importa porque desmonta una simplificación bastante extendida. No estamos escribiendo directamente sobre tablas Delta en OneLake ni usando el lake como backend transaccional milagroso. La aplicación entra por el endpoint SQL, habla TDS con el motor relacional y trabaja sobre una base operacional de verdad. Después, y de forma desacoplada, Fabric replica ese contenido hacia OneLake casi en tiempo real para exponerlo en formato Delta/Parquet al resto de motores analíticos de la plataforma. Esa réplica alimenta el SQL analytics endpoint y deja los datos listos para que Warehouse, Lakehouse, notebooks o Power BI los consuman sin castigar el workload OLTP.

Visto así, la arquitectura tiene dos caminos distintos. El primero es el camino operacional: aplicación, gateway, motor SQL, almacenamiento transaccional y gestión automática del servicio. El segundo es el camino analítico: mirroring hacia OneLake, persistencia en Delta/Parquet y consumo desde los motores analíticos de Fabric. Microsoft vende esto como translítico, y la palabra no es del todo humo, pero tampoco conviene leerla con demasiada poesía. No es que una misma estructura sirva simultáneamente para OLTP y analítica pesada sin coste ni compromiso. Lo que ocurre es algo bastante más sensato: el dato nace en una base transaccional y Fabric lo proyecta automáticamente a un formato analítico común dentro de OneLake. Eso está muy bien diseñado. Y, por una vez, el truco real es más interesante que el eslogan.

Aquí además hay un detalle importante para el bolsillo y para la operación. La replicación desde SQL Database en Fabric hacia OneLake no consume coste de cómputo en CUs, y Microsoft indica que ese mirroring tiene latencia cercana al tiempo real. Es decir, el servicio no solo evita que montemos una canalización adicional para mover datos al plano analítico, sino que además no penaliza la capacidad por el mero hecho de reflejarlos. El consumo empieza cuando explotamos esos datos con otros motores, no cuando Fabric los materializa en OneLake. Que esto sea así cambia bastante la conversación, porque convierte la réplica en una capacidad estructural del producto y no en otro peaje operativo disfrazado de integración “moderna”.

Desde el punto de vista de arquitectura de datos, la consecuencia práctica es muy clara. SQL Database en Fabric no es un sustituto de Warehouse ni de Lakehouse, sino una pieza intermedia que conecta el mundo operacional con el analítico sin obligarnos a montar fontanería extra desde el minuto uno. Su valor no está en reinventar el motor SQL, sino en empaquetar el plano transaccional y el plano analítico dentro del mismo perímetro de gobierno. Y ahí es donde realmente tiene sentido: menos integración manual, menos duplicidad de circuitos y más cercanía entre la base que da servicio y la plataforma que analiza. Siempre, claro, que tengamos presente que son dos representaciones coordinadas del dato y no una fusión mística entre OLTP y analítica que resuelva por arte de magia todos los pecados de diseño acumulados durante años.

¿Hasta qué punto es SQL de verdad?

La respuesta es bastante. Soporta el motor relacional que esperamos para OLTP, tablas transaccionales, ACID, claves foráneas, stored procedures, vistas, funciones, aislamiento desde serializable hasta read committed snapshot, tuning automático e indexación automática de non-clustered según datos reales de ejecución. Para cargas operacionales normales, APIs, aplicaciones internas, ODS y bases de datos de servicio, el sabor es inequívocamente SQL. No hay que reaprender a hablar con la base de datos porque alguien haya decidido que todo debía acabar en “Lake”.

Pero tampoco conviene venderlo como paridad plena con Azure SQL Database. No hay SQL Authentication, solo Microsoft Entra ID. No hay logins de servidor ni roles a nivel servidor. SQL Server Agent tampoco está, y Microsoft te empuja a pipelines o Airflow. Tampoco están failover groups, geo-restore, elastic jobs, elastic pools, DMS, DMA, VNet endpoints o resource pools. Además, hay restricciones de superficie, no se pueden crear tablas in-memory, ledger, ledger history ni Always Encrypted, y ciertas capacidades siguen en preview, como OPENROWSET BULK, full-text indexing o algunas opciones de ALTER DATABASE SET.

Hay otra diferencia sutil pero importante. Las consultas cross-database con nombres de tres partes existen, pero el camino soportado pasa por el SQL analytics endpoint, no por el plano transaccional clásico. Es decir, el “todo habla con todo” existe, sí, pero vive del lado analítico. Eso tiene lógica técnica (y en Azure SQL Database es igual), aunque desmonta un poco la fantasía de que toda la plataforma es un gran SQL Server cósmico con camiseta de Fabric. No lo es, por suerte y por desgracia.

El siguiente punto es donde muchos proyectos se ganan o se arruinan el presupuesto.

Escala y consumo: hasta dónde llega y cómo te cobra el chiste

Llegamos a la parte escabrosa. Microsoft coloca SQL Database en Fabric en el rango de datos estructurados de volumen moderado, desde varios GB hasta unos pocos TB. La documentación de arquitectura para Fabric lo sitúa claramente por debajo del terreno natural de Warehouse o Lakehouse cuando hablamos de big data, petabytes, tablas multi-TB y analítica masiva. Dicho sin diplomacia, esto no es el nuevo sitio donde aparcar todo “porque total también es SQL”. Para eso ya tenemos suficientes cementerios de arquitectura.

En límites documentados, la base puede escalar hasta 32 vCores y 4 TB, con tempdb de hasta 1.024 GB y throughput de escritura de log de hasta 50 MB/s. Además, Microsoft ha introducido un control máximo de vCores, aún en preview, que permite acotar el techo de cómputo. La propia documentación muestra el rango entre 4 y 32 vCores y deja claro que el servicio hace autoscaling dentro de ese tope. Es decir, escala sí, pero dentro de un corral bastante definido. Muy útil para OLTP moderno y ODS razonables. Menos impresionante cuando uno viene de leer promesas grandilocuentes sobre plataforma unificada universal definitiva, edición coleccionista.

El modelo de consumo es lo que cambia la conversación. Aquí no compras una base de datos con su SKU aislada, sino que consumes capacidad Fabric compartida. Un CU de Fabric equivale aproximadamente a 0,383 vCores de base de datos, y una F64 equivale a unos 24,512 vCores de SQL Database. La base autoscala, mantiene un mínimo de 2 GB de memoria mientras está online, y tras 15 minutos de inactividad para y libera CPU y memoria, eso sí, se sigue facturando el almacenamiento. Eso suena bien hasta que recuerdas que esa misma capacidad también la usan otros workloads de Fabric. Ahí empieza la parte divertida, en el sentido en que también es divertida una alerta a las tres de la mañana.

Particularidades de consumo de Fabric.

Los consumos en Fabric se dividen en dos tipos, los interactivos y los de background.

Y esto es un matiz decisivo, el consumo de SQL Database es una operación interactiva. En el Metrics app, “SQL Usage” recoge el coste de todas las sentencias T-SQL generadas por usuarios y por sistema, y cuando la capacidad entra en throttling las consultas pueden llegar a registrarse como rechazadas. Comparado con Warehouse, donde el consumo de consulta figura como background, aquí estamos mucho más cerca del mundo de la aplicación viva que del batch analítico tranquilo. 

Dicho en lenguaje DBA SQL Server, un consumo interactivo es el que nace de una petición que alguien espera resolver ya: una query lanzada desde la aplicación, desde SSMS, desde un informe o desde cualquier proceso que está dando servicio en ese momento. El consumo en segundo plano, en cambio, es el trabajo diferido que la plataforma puede ejecutar con otra prioridad, como una carga, una transformación o una consulta analítica que no está sosteniendo la interacción directa de un usuario. SQL Database en Fabric cae en el primer grupo porque está pensado para cargas operacionales y transaccionales, no para esperar disciplinadamente su turno.

La implicación es clara, hay que competir no solo con lo que se está ejecutando ahora mismo sino con la parte proporcional (Smoothing) de los trabajos de backgrpund que se han ejecutado en las últimas 24 horas. Y claro, si la capacidad va justa, el mayor impacto lo sufre el workload vivo, el de SQL que va a ver más latencia, menos estabilidad y posibles rechazos.

Con esto claro, ya se entiende mejor la parte económica y de seguridad.

Licenciamiento, acceso y la letra pequeña

Para usar SQL Database en Fabric necesitas una capacidad válida de Fabric, Power BI Premium o Trial, y la facturación sigue el modelo de capacidad de Fabric, no el de Azure SQL Database. Los cargos aparecen en Azure Cost Management bajo la suscripción asociada a la capacidad. El coste combina cómputo y almacenamiento, y el backup storage solo empieza a cobrarse cuando supera el 100 % del tamaño aprovisionado de la base. Es un modelo cómodo para centralizar gasto, pero borra la separación mental entre “mi base de datos” y “la capacidad del resto de Fabric”, que, para la analítica contable, no siempre es una buena noticia.

En cuanto a accesos la historia es sencilla pero dura a la vez, solo podemos usar Entra ID. No hay SQL logins. Tampoco hay sa. Ni hay ese usuario legacy que vive de cadenas de conexión con usuario y contraseña incrustados desde 2017 “porque nunca dio problemas”. Además, el acceso mezcla dos planos de autorización que hay que tener siempre en mente. Por un lado primero tenemos que tener acceso a Fabric y por otro los permisos nativos SQL dentro de la base. Microsoft lo describe como una superposición “most permissive”, así que conviene dar acceso de conexión con Fabric y afinar de verdad la seguridad en el plano SQL. Seguridad moderna, sí pero también fricción con aplicaciones legacy.

Aquí además hay una novedad relevante. Tras la FabCon de marzo de 2026, Microsoft ya recoge como GA la auditoría para SQL Database en Fabric y las customer-managed keys con TDE automático a nivel de workspace. También anuncia recuperación de bases borradas y retención de backup configurable entre 1 y 35 días. 

Y aquí conviene separar copia de seguridad operativa de disaster recovery real, porque no son lo mismo aunque a veces se vendan como si lo fueran. SQL Database en Fabric sí mantiene copias automáticas y permite point-in-time restore, con retención configurable entre 1 y 35 días, pero la restauración crea una base nueva dentro de Fabric y la documentación sigue marcando como no soportados el cross-workspace restore y el cross-region restore. Traducido al mundo real: estas copias nos cubren muy bien frente a borrados accidentales, despliegues fallidos o una metedura de pata humana, pero no son una copia portable que podamos sacar y restaurar donde nos convenga. Si queremos una estrategia de DR seria fuera de ese perímetro, hay que exportar periódicamente a BACPAC con SqlPackage y guardarlo en otra región o en almacenamiento externo, que es precisamente la vía que Microsoft recomienda para un fallo regional.

Con todo eso encima de la mesa, toca la pregunta estratégica.

¿Dónde sí tiene sentido el relato translítico y dónde todavía no?

Sí la pregunta es si todo esto de tener la parte operacional y la analítica juntas, si tiene sentido cuando quieres un núcleo operacional gobernado dentro de Fabric, con latencia baja, integridad relacional, tooling SQL clásico y una salida analítica casi inmediata sin montar otro circuito de ingestión. Ahí encajan muy bien los casos de uso que Microsoft empuja de forma oficial (Operational Data Store, reverse ETL y aplicaciones translíticas). También encaja para equipos que ya viven en Fabric y quieren reducir el impuesto de integración entre la base operativa y el resto del ecosistema.

Sin embargo, no tiene tanto sentido cuando tu analítica real pide otra cosa, grandes escaneos, históricos pesados, modelos dimensionales complejos, muchísima concurrencia analítica, ingestas masivas o escalado hacia decenas de TB y PB. Ahí Fabric ya tiene mejores piezas, y la propia guía de Microsoft coloca Warehouse, Lakehouse o Eventhouse por delante según el patrón. Tampoco lo veo claro si dependes de capacidades maduras de Azure SQL Database que aquí aún faltan, o si necesitas un aislamiento de red y de cómputo mucho más predecible. Una plataforma unificada ahorra fricción, sí, pero también puede concentrar demasiadas dependencias en el mismo sitio. Y eso solo parece bonito hasta el primer incidente transversal.

Queda la parte que de verdad ayuda a decidir.

Pros, contras y lo que le pediría para ser realmente usable

La principal virtud de SQL Database en Fabric es que resuelve muy bien el espacio intermedio que durante años ha quedado incómodo, una base operacional moderna, bastante SQL de verdad, con integración directa en la plataforma analítica y sin bricolaje constante entre servicios. Para ODS, serving layers, APIs, aplicaciones internas y escenarios donde OLTP y consumo analítico conviven cerca, la propuesta tiene mucho sentido. Y el hecho de que el espejo a OneLake salga de serie elimina una cantidad obscena de fontanería que hasta ahora hacíamos “a mano” y luego fingíamos que era arquitectura elegante.

Sus peajes están claros. La paridad con Azure SQL Database aún no es suficiente para ciertos entornos serios. El aislamiento de capacidad es más difuso. La dependencia de Entra ID para usuarios corta de raíz algunos escenarios heredados. La historia de red sigue siendo menos madura de lo que muchos equipos enterprise pedirían. Y la documentación aún necesita dejar de contradecirse entre tablas, notas de lanzamiento y páginas de detalle, algo normal para un producto tan nuevo por otra parte. Pero claro, en una solución que quiere ganarse a DBAs, eso no es un matiz: es parte del producto.

¿Qué le pediría para que sea más usable? 

Hasta aquí la teoría, ahora me voy a mojar y os voy a dar mi opinión. Para mi, para hacer esto más usable pediría menos épica y más nitidez. Quiero una matriz de compatibilidad viva y fiable, una historia de migración de primera división, con DMS o equivalente real. Por pedir, quiero una solución de DR más madura, con geo-restore y failover groups. Y por supuesto, aislamiento y control de red más directos por base o por workspace sin rodeos ceremoniales. Quiero observabilidad y gobierno de capacidad cada vez más finos, porque cuando una base operacional compite por CUs con media plataforma, el problema ya no es solo de base de datos, es de convivencia. Y la convivencia, como sabemos, nunca se arregla con una keynote.

SQL Database en Fabric no viene a sustituir Azure SQL Database ni a jubilar Warehouse. Viene a ocupar un hueco muy concreto, y cuando encaja, encaja francamente bien. Pero conviene comprarla por lo que es, no por lo que el folleto insinúa. Es SQL, sí. Bastante SQL. Pero sobre todo es SQL bajo las reglas de Fabric. Y esa diferencia es exactamente la que decide si esto acaba siendo una gran pieza de plataforma o una bonita confusión con logo nuevo.

Conclusión

SQL Database en Fabric me parece una solución bastante más seria de lo que algunos quieren admitir, pero también bastante menos mágica de lo que Microsoft insinúa. Cuando el problema es OLTP moderado, integración rápida con analítica y menos fontanería entre la base operacional y el resto de la plataforma, encaja muy bien. Cuando necesitamos aislamiento más predecible, una historia de DR madura, más superficie de Azure SQL y menos dependencia del estado general de la capacidad, todavía no. No viene a sustituirlo todo, ni falta que hace. Viene a ocupar un hueco concreto, y ahí puede funcionar francamente bien. El error sería comprar el eslogan en lugar de entender la pieza. Porque aquí la diferencia importante no es que sea SQL. La diferencia importante es bajo qué reglas juega ese SQL.

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

Publicado por Roberto Carrancio en Cloud, SQL Server, 0 comentarios
DiskANN: búsqueda vectorial en SQL Server 2025

DiskANN: búsqueda vectorial en SQL Server 2025

SQL Server 2025 ya no mira los vectores desde la barrera. Tenemos tipo vector, búsqueda exacta con VECTOR_DISTANCE y búsqueda aproximada con CREATE VECTOR INDEX y VECTOR_SEARCH, todo ello sobre una implementación que Microsoft vincula de forma explícita con DiskANN. No es una curiosidad de laboratorio ni una feature ornamental para demos con palabras como “semántico” y “copilot” repetidas ocho veces por minuto. Es una apuesta técnica real, todavía con partes en preview, pero real.

Ahora bien, una cosa es que el motor ya hable vectores y otra muy distinta que haya dejado de ser SQL Server. Cuando mezclamos embeddings con filtros relacionales, claves, joins y diseño físico serio, aparecen las costuras. Y eso es precisamente lo interesante. Porque el problema no está en que exista DiskANN, sino en cómo aterriza dentro de un motor que no nació para resolver búsquedas de proximidad semántica sobre datos de negocio.

Antes de entrar en el índice, conviene situar el problema. No para repetir una introducción de manual sobre IA, sino para dejar claro por qué esta funcionalidad no se comporta como un índice relacional clásico con mejor prensa.

Vectores, embeddings y kNN: no buscamos igualdad, buscamos cercanía

Un vector es una secuencia ordenada de números. En SQL Server se almacena con el tipo vector, que internamente usa un formato binario optimizado, aunque se presente como array JSON por comodidad. Cada dimensión ocupa 4 bytes en float32 y SQL Server 2025 añade soporte preview para float16, lo que abre una vía interesante para reducir espacio cuando la precisión lo permita.

Un embedding es un caso particular de vector: una representación numérica que intenta capturar características relevantes de un texto, una imagen o cualquier otro objeto. La consecuencia práctica es que dejamos de preguntar “¿es igual?” o “¿está entre A y B?” y pasamos a preguntar “¿qué está cerca de esto?”. Ese cambio parece pequeño hasta que toca ejecutar consultas. Entonces deja de ser filosofía y pasa a ser coste.

Cuando resolvemos el problema de forma exacta, hacemos un kNN exacto: calculamos distancia contra todos los candidatos y nos quedamos con los más próximos. Microsoft lo describe así en su documentación y, además, deja una recomendación bastante útil: la búsqueda exacta sigue teniendo sentido cuando el conjunto efectivo a revisar es pequeño, con una orientación general de unas 50.000 filas o menos después de aplicar predicados. Ese matiz importa mucho, porque evita uno de los errores más comunes: asumir que lo aproximado siempre es mejor solo porque tiene nombre moderno.

Hasta aquí el problema. La siguiente pregunta es evidente: cuando el volumen crece, ¿cómo evitamos que cada consulta termine pareciendo una inspección completa del universo conocido?

DiskANN: el “cómo” de Microsoft para escalar búsqueda vectorial

La respuesta de Microsoft para búsqueda aproximada es DiskANN. Conviene decirlo bien para no arrastrar una imagen mental equivocada: DiskANN no ordena vectores como si fuera un B-tree extraño. Es una familia de algoritmos de ANNS basada en grafos, pensada para combinar precisión razonable, baja latencia y uso eficiente de memoria y SSD. Microsoft Research describe Project Akupara precisamente como una línea de trabajo orientada a escalar ANNS para búsqueda y recomendación a gran escala, y la publicación original de 2019 presenta DiskANN como un sistema capaz de indexar y buscar mil millones de puntos en una sola máquina usando 64 GB de RAM y SSD.

La gracia de ese enfoque no es solo académica. Microsoft ha ido consolidando DiskANN como base tecnológica en su ecosistema, y SQL Server 2025 lo expone directamente en CREATE VECTOR INDEX con TYPE = ‘DISKANN’. En otras palabras, no estamos ante una abstracción vaporosa; el motor te deja tocarla. Y eso siempre es más útil que una promesa de roadmap adornada con iconos de nube.

Pero aquí aparece el primer matiz importante para cualquiera que haya peleado con diseño físico de tablas. Que SQL Server pueda almacenar vectores no significa que debamos meterlos sin pensar en la tabla principal, como si una fila de negocio de 100 bytes y un embedding de 1.536 dimensiones fuesen vecinos naturales. No lo son.

La decisión de modelado que más sentido tiene

Mi recomendación sigue siendo separar los embeddings en una tabla satélite 1:1 respecto a la tabla relacional principal. No por postureo arquitectónico, sino porque el coste físico de mezclar ambas cosas en la misma fila es demasiado alto para fingir que no existe. Microsoft lo explica con un ejemplo muy claro en su FAQ: una página de datos admite hasta 8.060 bytes (8 Kb) y un vector de 1.024 dimensiones en float32 ocupa 4.104 bytes contando cabecera, lo que ya limita a un solo vector por página. Si subimos dimensiones, el efecto no mejora precisamente.

Eso no significa solo “más fragmentación”, que es la forma rápida de contarlo. Significa también menos densidad de página, más I/O, peor aprovechamiento de caché y más penalización para accesos relacionales que no necesitan tocar el embedding. La guía de diseño de índices de SQL Server insiste en mantener los índices, en especial el clustered, lo más estrechos posible. Y aquí conviene recordar algo bastante básico, si la tabla principal sirve para OLTP o para consultas relacionales normales, meter una columna vectorial enorme ahí dentro porque “ya la usamos en algunas búsquedas” es una forma muy creativa de castigar todo lo demás.

Hay además un segundo argumento, todavía más relevante porque los índices vectoriales aproximados y VECTOR_SEARCH siguen en preview dentro de SQL Server 2025. La tabla que tiene un vector index debe tener una PK clustered entera de una sola columna y, mientras el índice exista, esa tabla queda de solo lectura. En SQL Server 2025 no está disponible ALLOW_STALE_VECTOR_INDEX, así que si cambian los datos toca recrear el índice. Separar embeddings en una tabla 1:1 aísla esa rigidez donde corresponde y evita congelar la tabla relacional principal.

De hecho, el propio material de Microsoft ya deja entrever ese patrón. En los ejemplos de VECTOR_SEARCH aparece una tabla llamada wikipedia_articles_embeddings, lo que sugiere un diseño separado para los vectores frente al contenido relacional o documental. No es una norma escrita en piedra, pero tampoco parece casualidad.

Una vez aceptado ese modelo, la consulta cambia. Y ahí es donde aparece la parte realmente delicada: el JOIN mejora el diseño físico, pero no convierte mágicamente la búsqueda vectorial en algo filter-aware.

¿Qué cambia en la consulta cuando el embedding vive fuera?

Con un diseño 1:1, lo lógico es que VECTOR_SEARCH se ejecute sobre la tabla de embeddings y luego se haga JOIN con la tabla relacional para recuperar atributos de negocio y aplicar filtros. Ese patrón encaja perfectamente con el estado actual del producto, porque VECTOR_SEARCH solo admite tablas base; no puede apuntar a una vista ni a una tabla temporal intermedia ya filtrada. Además, si encuentra un índice ANN compatible en esa columna y con la misma métrica, lo usa; si no, vuelve a kNN.

La consulta natural sería algo así:

Esto está bien desde el punto de vista del modelo físico. La tabla principal sigue estrecha, el índice vectorial vive donde debe y el JOIN 1:1, sobre una clave entera, normalmente será barato. Lo que no cambia es el orden lógico de la operación ANN respecto a los predicados de negocio. Y ahí vuelve a entrar el jarro de agua fría.

El JOIN no arregla el post-filter: solo mueve la frontera

La documentación de VECTOR_SEARCH es bastante clara, la búsqueda vectorial ocurre antes de aplicar cualquier predicado, y los filtros adicionales se evalúan después de devolver los vecinos más similares. Microsoft lo llama post-filter only. Si pides TOP_N = 10 y luego filtras por TenantId o IsActive, puedes acabar con menos de 10 filas o incluso con ninguna. Eso vale igual si el filtro cae sobre la misma tabla que contiene el embedding o si llega desde una tabla relacionada mediante JOIN. El JOIN no cambia esa semántica.

Eso significa que separar embeddings sí mejora el diseño físico, pero no resuelve la principal limitación actual del ANN en SQL Server 2025. El trabajo más caro sigue siendo la navegación del índice DiskANN sobre el conjunto indexado. Si después un filtro de negocio es muy selectivo, la forma de compensarlo suele ser sobremuestrear, subiendo TOP_N para tener suficientes candidatos válidos tras el JOIN y el WHERE. Y cuanto más selectivo sea el filtro, más se erosiona la ventaja del índice aproximado.

Aquí conviene evitar una confusión habitual. El problema no es que el JOIN sea especialmente caro. En una relación 1:1 bien indexada, no suele serlo. El problema es que el filtro relacional llega tarde para ayudar a la fase ANN. Y eso no lo arregla un buen modelo lógico, porque es una consecuencia de cómo está implementado el acceso vectorial hoy en el motor.

La pregunta entonces ya no es si separar la tabla, que para mí sí, sino cuándo conviene dejar de insistir con ANN y volver, sin complejos, a la búsqueda exacta.

¿Por qué no puedes filtrar antes sin romper algo?

La base técnica está bien documentada en la investigación de Microsoft sobre Filtered-DiskANN. El problema de los filtros en ANNS no se resuelve bien tocando solo la fase de búsqueda. El paper critica precisamente el enfoque de postprocesado, porque para filtros de baja especificidad puede obligar a recuperar muchísimos candidatos antes de encontrar uno que cumpla el predicado. Pero también deja claro que la solución trivial de “un índice por filtro” no escala.

La razón de fondo es que un índice tipo DiskANN es un grafo global construido sobre todos los vectores. Durante la búsqueda, algunos nodos no son resultados finales interesantes, pero sí son cruciales como puentes para navegar hacia la zona correcta del espacio vectorial. Si yo aplico un filtro arbitrario antes de buscar y elimino parte de esos nodos, puedo romper la navegabilidad efectiva del grafo. Dicho de otra manera, el subconjunto filtrado no conserva necesariamente las propiedades del índice original. Y eso afecta al recall, al coste o a ambas cosas.

Por eso filtered ANN de verdad no consiste en “empujar el WHERE hacia abajo” y quedarse tan ancho. Requiere índices diseñados con consciencia de filtro, estrategias de construcción distintas o, al menos, estructuras auxiliares que permitan no destrozar el recorrido. Ahí es donde la investigación va por delante de la implementación actual del motor. Y sinceramente, mejor admitirlo que fingir que un predicado sobre TenantId va a integrarse solo por buena voluntad. También podríamos confiar en MERGE en producción, pero cada uno gestiona sus traumas como puede.

A partir de aquí, la pregunta útil deja de ser “siempre ANN o siempre exacto” y pasa a ser mucho más adulta: “¿cuándo me compensa cada enfoque en SQL Server 2025?”.

¿Cuándo DiskANN tiene sentido y cuándo es mejor volver a VECTOR_DISTANCE?

Si el corpus es grande y los filtros relacionales son débiles, VECTOR_SEARCH con DiskANN es la opción natural. Para eso existe, para reducir latencia y coste frente a un barrido completo. Pero si el patrón real de consulta siempre filtra fuerte por cliente, estado, región o cualquier dimensión de negocio que reduzca mucho el conjunto efectivo, entonces la recomendación oficial de Microsoft cobra todo el sentido del mundo: cuando acabas buscando sobre 50.000 vectores o menos, la búsqueda exacta con VECTOR_DISTANCE vuelve a ser una alternativa muy seria.

Y aquí, precisamente, la separación 1:1 juega a favor. Puedes filtrar primero en la tabla relacional, quedarte con el subconjunto de negocio que realmente importa, hacer el JOIN hacia la tabla de embeddings y calcular distancias solo sobre esos candidatos. Es una estrategia mucho más alineada con el optimizador relacional clásico y, además, evita pedirle a un índice ANN global que resuelva con elegancia algo para lo que no ha sido diseñado. Sí, podemos pedirle heroicidades a cualquier índice. También podemos confiar en que un SELECT * no acabará mal. Pero a cierta altura de la película conviene distinguir entre esperanza y diseño.

Conclusión

SQL Server 2025 ha dado un paso importante con búsqueda vectorial nativa y DiskANN como base técnica para ANN. Eso merece atención, pruebas serias y bastante menos folklore del habitual. Pero también conviene poner cada pieza en su sitio. El mejor diseño, en la práctica, pasa por separar embeddings en una tabla 1:1, mantener la tabla relacional principal estrecha y aislar ahí las restricciones operativas del vector index. Eso es buena ingeniería de datos, no una preferencia estética.

Ahora bien, ese diseño no convierte VECTOR_SEARCH en filtered ANN. Hoy seguimos teniendo ANN global más post-filter relacional, aunque el filtro llegue por JOIN. Esa diferencia condiciona rendimiento, recall y criterio de uso. Entenderla no es un detalle menor. Es la diferencia entre usar la novedad con cabeza o descubrir, demasiado tarde, que el motor no estaba haciendo exactamente lo que tú dabas por supuesto. Y en bases de datos, como sabemos todos, dar cosas por supuestas suele salir carísimo.

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

Publicado por Roberto Carrancio en Cloud, Índices, Rendimiento, SQL Server, 0 comentarios
TDE, Always Encrypted, TLS y DDM: cuándo usar cada uno

TDE, Always Encrypted, TLS y DDM: cuándo usar cada uno

Lo sé, llevo más de un mes sin publicar artículos en el blog. No ha sido precisamente porque se me haya olvidado escribir. He estado metido hasta el cuello preparando cursos y sacando adelante otros proyectos. Parece que no pero estas cosas consumen las horas con una eficacia admirable y una compasión nula. El canal de YouTube también lleva cerca de dos meses parado por lo mismo. Así que vamos a retomar ambas cosas poco a poco, con algo que además creo que merece la pena.

Hace unos días me dejaron un comentario bastante sensato en un vídeo antíguo donde explicaba cómo funciona TDE en SQL Server. @eadames2 venía a decir que el vídeo era demasiado básico y que faltaban ejemplos de vida real, contexto y, sobre todo, criterio para decidir qué método de cifrado conviene según el caso. Y sí, la observación es buena. En el vídeo enseñé el cómo. Hoy, aquí, toca hablar del cuándo y del para qué.

Sobre cifrado ya había publicado en el blog el artículo “Cifrado de datos en SQL Server”, donde repasaba TLS, TDE, Always Encrypted y DDM, y también otro texto sobre qué pasa con la inicialización instantánea de ficheros al habilitar TDE, que entra en una consecuencia operativa muy concreta. No voy a repetir aquí lo que ya está explicado allí. Lo que quiero hacer en este artículo es completar esa parte que muchas veces se queda fuera: cómo elegir bien según la amenaza real, el tipo de datos y el precio operativo que estamos dispuestos a pagar.

Antes de entrar en tecnologías concretas, conviene poner orden en una idea que suele venir torcida de fábrica.

El error habitual: elegir tecnología antes de definir la amenaza

Cuando alguien dice que quiere “cifrar la base de datos”, casi nunca está describiendo un requisito. Está describiendo una intuición. Y las intuiciones, en seguridad, suelen ser un punto de partida aceptable y un punto de llegada desastroso.

No es lo mismo proteger datos frente al robo de backups que impedir que un DBA vea una información concreta. Tampoco es lo mismo ocultar información sensible a usuarios funcionales que proteger el tráfico entre aplicación y servidor. Meter todo eso en la misma cesta y resolverlo con una sola tecnología suele acabar como acaban estas cosas: con un checkbox marcado, una falsa sensación de seguridad y una reunión desagradable más adelante.

TDE, Always Encrypted, TLS y DDM no compiten entre sí en todos los escenarios. A veces se complementan y a veces uno no sirve absolutamente para nada si el problema real está en otra parte. Por eso la pregunta correcta no es “qué método cifra más”. La pregunta correcta es “de qué quiero protegerme exactamente y quién es el adversario aquí”.

Ese matiz cambia bastante la conversación, así que vamos con casos reales.

Caso real 1: alguien roba un backup o se lleva los ficheros de la base de datos

Aquí es donde TDE encaja muy bien. Si tu preocupación principal es que alguien coja un .bak, un .mdf, un .ndf o un .ldf y se lo lleve alegremente a otro servidor para restaurarlo sin permiso, TDE es una solución razonable y bastante transparente.

La gran ventaja de TDE es precisamente esa, protege los datos en reposo sin obligarte a rediseñar la aplicación ni a reescribir consultas. El usuario legítimo sigue trabajando igual, y el motor cifra y descifra por debajo. En entornos corporativos donde el riesgo está en el acceso físico a discos, en copias de seguridad mal custodiadas o en procedimientos de restauración fuera de control, TDE tiene muchísimo sentido.

Ahora bien, conviene no pedirle milagros. TDE no evita que alguien con acceso lógico a la base de datos y permisos suficientes consulte los datos. No te protege de un SELECT bien autorizado, ni de un desarrollador con más privilegios de los que debería, ni de ese clásico “es solo para una prueba” que termina con un usuario leyendo más de la cuenta. TDE protege archivos y backups. No arregla un modelo de permisos chapucero, que es otra afición muy extendida.

Además, TDE tiene peaje. Ya conté en el artículo sobre inicialización instantánea de ficheros que al activarlo perdemos ciertas ventajas operativas y podemos notar impacto en crecimientos, restauraciones y tiempos de ciertas operaciones. Y hay otro punto que en las demos parece un detalle y en producción no lo es, el certificado. Si no haces backup del certificado y de su clave privada, el día que necesites restaurar en otro servidor te puedes ahorrar el café, porque no vas a restaurar nada. Si cifras con TDE y no haces copia de seguridad del certificado y de su clave privada, tu backup queda protegido incluso contra ti mismo. Muy seguro todo. Muy poco práctico también.

No todos los escenarios exigen llegar tan lejos. A veces el problema no es que alguien robe un backup, sino que ciertos usuarios ven más de lo que deberían ver en el día a día.

Caso 2: algunos usuarios no deberían ver los datos completos

Aquí entra DDM, y aquí conviene hablar sin maquillaje, porque bastante tiene la tecnología con el suyo propio. Dynamic Data Masking no es cifrado. Sirve para ocultar parcialmente datos cuando los consulta un usuario que no tiene permiso para verlos completos, pero el dato sigue estando almacenado tal cual en la base de datos.

Eso puede ser útil. En entornos de soporte, atención al cliente, explotación o consultas operativas, muchas veces necesitamos que ciertos perfiles trabajen con datos reales sin exponerles el valor completo de una columna sensible. Mostrar parte de un correo, ocultar tramos de un teléfono o limitar la visualización de un identificador puede tener bastante sentido. Reduce la exposición innecesaria y evita errores humanos bastante previsibles.

El problema viene cuando alguien intenta vender DDM como si resolviera necesidades de seguridad de alto nivel. No. DDM no protege un backup robado, no impide que el motor lea el dato y no sustituye ni a TDE ni a Always Encrypted. Lo que hace es modificar la presentación del dato para determinados usuarios. Nada más. Nada menos. Y mientras entendamos eso, puede ser perfectamente válido.

En otras palabras: si tu requisito es “que el personal de soporte no vea el valor completo del documento de identidad”, DDM puede encajar. Si tu requisito es “que nadie pueda extraer el dato real de la base si accede a los ficheros” o “que ni siquiera ciertos administradores puedan verlo”, entonces DDM no te sirve. Ahí ya estarías usando una cortina para parar un incendio, que tiene un punto optimista, pero poca eficacia.

A veces, sin embargo, el nivel de exigencia sube bastante más. Y en ese punto ya no basta con ocultar visualmente una parte del dato.

Caso 3: ni siquiera el DBA debería ver ciertos datos

Cuando el requisito es que ni siquiera un administrador de base de datos pueda consultar ciertos valores en claro, TDE y DDM se quedan cortos por definición. TDE porque el motor necesita descifrar la información para trabajar con ella. DDM porque solo maquilla la salida para algunos usuarios. Si el objetivo es separar de verdad quién puede ver el dato y quién solo puede administrarlo, entonces toca mirar Always Encrypted.

Always Encrypted está pensado para columnas concretas y para escenarios donde los datos son especialmente sensibles. Hablamos de información médica, datos financieros, salarios, identificadores personales o cualquier campo donde el requisito no sea simplemente proteger discos y copias de seguridad, sino impedir que ciertos perfiles con acceso a la infraestructura puedan leer el valor real. Es una diferencia importante.

La gracia de Always Encrypted está precisamente en esa separación. Las claves no se gestionan como en TDE y el modelo de confianza cambia bastante. Eso hace que el nivel de protección sea superior en algunos escenarios, pero también introduce complejidad técnica. No todas las consultas pueden hacerse igual, no todas las operaciones son cómodas y el desarrollo de la aplicación tiene que convivir con esas limitaciones. Vamos, que no es una opción para activar con alegría y descubrir el lunes que media aplicación ya no hace lo que hacía.

¿Merece la pena? Cuando el requisito lo exige, sí. Sin duda. Pero si solo quieres proteger backups o discos, meter Always Encrypted porque suena más serio es un error clásico. Aún hay quien cree que la mejor solución de seguridad es siempre la más compleja. 

Hasta ahora hemos hablado de datos en reposo o de visibilidad dentro de la propia base. Pero queda una capa distinta, y conviene separarla para no mezclar churras con certificados.

Caso real 4: el problema está en la red, no en el disco

Si los datos viajan entre cliente y servidor, o entre servicios, y tu preocupación es que alguien intercepte ese tráfico, entonces lo que necesitas mirar es TLS. Ni TDE ni DDM ni Always Encrypted sustituyen el cifrado del canal de comunicación.

Esto es importante porque sigue habiendo entornos donde alguien activa TDE y se queda tan tranquilo, como si eso protegiera automáticamente el tráfico por red. No. Cifrar el archivo de base de datos no convierte mágicamente en segura una conexión mal configurada. Son capas distintas y problemas distintos.

TLS es el mecanismo adecuado para proteger datos en tránsito. Si tienes aplicaciones conectadas por red, servicios hablando entre sí, réplicas, integraciones o accesos remotos, necesitas que el canal vaya cifrado y, si puede ser, bien configurado. Lo contrario es confiar en que nadie mire. Una estrategia audaz, desde luego, aunque no especialmente profesional.

En muchos sistemas reales, de hecho, la respuesta correcta no es elegir una sola opción, sino combinar varias con un poco de sentido común.

Lo normal en producción: combinar tecnologías según el riesgo real

La mayoría de entornos serios no viven de absolutos. Viven de compromisos bien pensados. Y eso significa que muchas veces la solución buena no es “TDE o Always Encrypted”, sino “TDE y además TLS”, o “TLS para todo el tráfico y DDM para ciertos usuarios de soporte”, o incluso “TDE para la base completa y Always Encrypted en columnas concretas especialmente sensibles”.

Un ERP corporativo puede necesitar TDE para proteger backups y discos, TLS para las conexiones y un modelo de permisos competente. Un sistema de recursos humanos probablemente necesite además evaluar Always Encrypted en campos muy concretos. Una herramienta de soporte interno puede apoyarse en DDM para limitar lo que ciertos perfiles ven en pantalla, sin vender eso como la octava maravilla de la seguridad.

La decisión, por tanto, no depende tanto del “tipo de base de datos” en abstracto como del tipo de dato, del modelo de acceso y del riesgo que quieres mitigar. Una base de datos de facturación interna y una de analítica pueden tener tamaños parecidos y una necesidad de protección totalmente distinta. El nombre del sistema importa menos que la exposición del dato y las personas que pueden tocarlo.

Con todo esto encima de la mesa, ya se puede responder de forma más útil a la pregunta del comentario.

Entonces, ¿qué método conviene según el caso?

Si lo que te preocupa es el robo de copias de seguridad o de archivos físicos, empieza por TDE. Es la opción natural, tiene un impacto asumible en muchos entornos y protege precisamente ese escenario. Si además activas TDE, haz bien el trabajo completo: gestiona certificados, documenta el procedimiento de recuperación y prueba restauraciones. Porque un TDE sin backup del certificado es una bomba de relojería con modales.

Si el problema es que determinados datos no deberían estar visibles ni para personal con privilegios altos en la infraestructura, entonces TDE no basta y hay que mirar Always Encrypted. Aquí la complejidad sube, sí, pero también lo hace el nivel de aislamiento real.

Si lo que necesitas es limitar lo que ven ciertos usuarios operativos sin cambiar la aplicación de arriba abajo, DDM puede ser útil, sabiendo perfectamente que no estás cifrando nada. Y si la preocupación es el tráfico entre cliente y servidor, no des más rodeos y configura TLS como corresponde.

Dicho de otra manera: TDE protege datos en reposo a nivel de archivos y backups. Always Encrypted protege columnas frente a accesos que no deberían ver el valor real. DDM limita exposición visual en consultas para ciertos perfiles. TLS protege el canal de comunicación. Confundirlos es bastante común. Elegirlos bien ya no debería serlo tanto.

Conclusión

El comentario de @eadames2 iba bien encaminado: enseñar a activar TDE está bien, pero no basta si no explicamos cuándo tiene sentido usarlo y cuándo no. En seguridad, la tecnología correcta no es la más llamativa ni la más compleja, sino la que responde a la amenaza real sin convertir la operación diaria en una penitencia.

Ya tenemos en el blog el artículo general sobre cifrado en SQL Server y el análisis del impacto de TDE sobre la inicialización instantánea de ficheros. Este texto viene a completar esa parte que faltaba: el criterio. Porque cifrar por cifrar sirve de poco. Lo útil es saber qué proteges, de quién lo proteges y cuánto te va a costar mantenerlo. Lo demás es decorar el problema con terminología bonita y esperar que nadie haga demasiadas preguntas.

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

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