Rendimiento

¿Por qué los linked server rinden tan mal?

Pocas tecnologías han generado tanto rechazo silencioso como los linked servers en SQL Server. Todos los hemos usado alguna vez. Algunos hasta han intentado montar arquitecturas enteras con ellos, como si fueran túneles mágicos entre bases de datos. Y al principio todo parece funcionar… hasta que dejas de hacer pruebas con tablas de 100 filas y llega la realidad con sus millones. Entonces empieza el verdadero drama.

Hoy vamos a entrar al barro y a entender por qué los linked servers rinden tan mal, qué está pasando por debajo y qué alternativas tenemos cuando necesitamos algo que se parezca a un acceso remoto entre instancias. Pero, antes de abrir el quirófano y diseccionar por qué rinden tan mal, conviene repasar qué son exactamente.

¿Qué es un linked server?

Un linked server es una funcionalidad de SQL Server que nos permite acceder a datos almacenados en otro origen, como si fuera parte de nuestra propia instancia. Ese origen puede ser otra instancia de SQL Server, una base de datos Oracle, un Excel, un archivo Access o cualquier cosa que tenga un proveedor OLE DB compatible. Básicamente, nos permite ejecutar consultas distribuidas, mezclando datos de aquí y de allá, sin necesidad de ETL ni procesos intermedios.

Desde el punto de vista del T-SQL, un linked server actúa como un alias remoto: podemos hacer SELECT o incluso INSERT, UPDATE y DELETE sobre objetos que realmente viven en otro servidor. Todo a través de una conexión que, en apariencia, es transparente. Y aquí está el engaño.

Porque aunque la sintaxis parezca sencilla y todo pinte bien en la teoría, por debajo SQL Server tiene que hacer auténticas acrobacias para que eso funcione. Y, como veremos, muchas veces no lo consigue sin romperse una pierna por el camino.

Linked Server o lo que parece una buena idea en realidad es una trampa

El concepto es tentador. Con un linked server podemos conectarnos desde una instancia de SQL Server a otra, e incluso a otros motores de base de datos, como si todo estuviera en la misma base. Basta con un SELECT * FROM [ServidorRemoto].[Base].[Esquema].[Tabla] y listo. Bonito, directo, sin middleware.

Claro, si obviamos que la consulta puede tardar más que una auditoría fiscal y que la mitad de las optimizaciones que teníamos en mente se van por el desagüe.

El optimizador no es adivino

Uno de los mayores problemas de rendimiento viene de cómo SQL Server construye el plan de ejecución cuando hay un linked server de por medio. El optimizador necesita estadísticas para tomar decisiones inteligentes, pero cuando apuntamos a un servidor remoto, las estadísticas simplemente no están ahí. SQL Server no sabe cuántas filas hay, ni cuán selectiva es una condición, ni si merece la pena hacer un join remoto o traerse toda la tabla localmente.

¿Y qué hace cuando no sabe? Apuesta. Y como buen jugador conservador, apuesta mal.

Acaba generando planes de ejecución mediocres, que podrían parecer aceptables en una tabla de juguete, pero que se vuelven un desastre cuando la tabla tiene un volumen real. ¿El resultado? Lecturas innecesarias, joins ejecutados en el servidor equivocado y un tráfico de red digno de una transferencia de backups.

Linked Server PUSH vs PULL

Otro elemento clave es cómo SQL Server decide si “empuja” partes de la consulta al servidor remoto (lo que se conoce como pushing de consultas) o si tira de los datos hacia el local (pulling). Idealmente, querríamos que SQL Server enviase una subconsulta bien formada al servidor remoto, para que allí se ejecute lo que tiene sentido y solo nos devuelva lo necesario. Algo así como el plegado de consultas de Power BI.

Pero no. Muchas veces SQL Server prefiere traerse toda la tabla (sí, toda) al servidor local, y luego aplicar filtros, joins o agregaciones. Porque eso de optimizar entre servidores diferentes le cuesta. Y mucho.

Si alguna vez has hecho un SELECT COUNT(*) contra un linked server y has notado que tarda minutos en devolver un número, ya sabes por qué: se está trayendo todas las filas. A pelo.

El drama del Linked Server Provider

Detrás de cada linked server hay un proveedor OLE DB. Y no todos son iguales. Algunos soportan operaciones remotas de forma más o menos decente. Otros se comportan como si los hubiera programado un becario en prácticas en 1998.

Por ejemplo, el proveedor nativo de SQL Server a SQL Server (SQLNCLI o MSOLEDBSQL) tiene ciertas optimizaciones, pero ni con esas nos salva del todo. Ahora bien, si conectamos a Oracle, MySQL o, que no tengáis que sufrirlo, Access, el comportamiento puede ser completamente errático. Y la culpa, en parte, es del proveedor.

Si el proveedor no soporta pasar consultas completas o no permite ciertas operaciones, SQL Server lo suple como puede: trayendo filas, aplicando funciones en local, y rezando para que la red no esté saturada.

Transacciones distribuidas: el infierno del Linked Server

Si a alguien se te ocurre la brillante idea de meter una transacción que toque un linked server, prepárate. Entramos en terreno de las transacciones distribuidas, y con ello en el mundo de MSDTC (Microsoft Distributed Transaction Coordinator), uno de los servicios más temperamentales de Windows.

¿Funciona? A veces. ¿Es rápido? Ni de lejos. ¿Es seguro? Mejor no responder. Configurar MSDTC entre servidores, sobre todo en entornos con firewalls o clusters, es un vía crucis. Y aunque consigas que funcione, el rendimiento se desploma por la sobrecarga que supone coordinar commit y rollback entre servidores.

JOINs remotos: lo peor que puedes hacer por un Linked Server

Uno de los errores más frecuentes (y más costosos) es hacer joins entre una tabla local y otra remota. En muchos casos, SQL Server se trae la tabla entera del servidor remoto al local y luego hace el join. Si la tabla remota tiene 10 millones de filas y solo necesitábamos 5, mala suerte. Ya es tarde.

El problema se agrava si el join es sobre columnas sin índices en el remoto, o si las condiciones están ocultas tras funciones (como CAST, CONVERT o UPPER). SQL Server no es capaz de generar una consulta remota eficiente y recurre al “me lo traigo todo y ya lo filtro aquí”. Un plan perfecto si tienes acciones del proveedor de ancho de banda.

¿Hay alguna solución?

Sí, pero no mágica. La primera es no usar linked servers para consultas complejas o de alto volumen. Son útiles para tareas administrativas, sincronizaciones puntuales o lecturas ligeras. Pero si necesitas integrar datos entre sistemas, mejor piensa en otras alternativas.

Replicación, ETL, servicios web o incluso bases de datos distribuidas con lógica de federación (sí, esas cosas raras de Azure SQL) pueden ser opciones más razonables. También puedes optar por staging: traer datos relevantes a una tabla temporal o staging local antes de hacer las operaciones serias.

Otra alternativa, cuando no hay más remedio, es escribir consultas distribuidas con OPENQUERY. Esto obliga a que la consulta se ejecute remotamente, evitando que SQL Server decida hacer el pull de datos. No es bonito, pero al menos sabes lo que estás haciendo.

Y, por supuesto, nunca, nunca asumas que lo que funcionó en desarrollo (con 1.000 filas) escalará igual en producción (con 100 millones). No con linked servers. No hoy. No nunca.

Conclusión

Los linked servers tienen su lugar, pero no es en el rendimiento. Son una herramienta más, no la solución universal. Cuando los usamos sin entender sus limitaciones, el castigo no tarda en llegar. Lo hemos visto demasiadas veces: servidores que colapsan, redes saturadas, y consultas que mueren de inanición esperando datos que jamás deberían haber salido del servidor remoto.

¿Se pueden usar? Claro. Pero como todo en SQL Server, con cabeza, y sabiendo que a veces es mejor copiar datos que pretendiendo unir mundos que no están hechos para unirse en tiempo real.

Porque sí, puedes hacer joins entre servidores remotos, pero también puedes correr descalzo por un campo de cactus y ninguna de las dos opciones es recomendable

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, Rendimiento, SQL Server, 0 comentarios

¿Qué es CLR y por qué deberías limitar su uso?

Hay tecnologías en SQL Server que parecen diseñadas por y para gente que odia SQL. Una de ellas es CLR (Common Language Runtime). Si alguna vez has pensado “¿y si meto C# dentro de una base de datos?”, enhorabuena: acabas de invocar al diablo del rendimiento. Pero, como todo en este mundo, si se usa con criterio y sentido común (sí, eso que tan poca gente aplica), puede aportar soluciones que de otro modo serían infernales de implementar en T-SQL.

¿Qué demonios es CLR y por qué está dentro de SQL Server?

CLR es un motor de ejecución de .NET. Desde SQL Server 2005 tenemos la opción de crear procedimientos almacenados, funciones, triggers y tipos definidos por el usuario usando lenguajes .NET como C# o VB.NET. Esto permite extender la funcionalidad de T-SQL cuando éste se queda corto. Y sí, hay que reconocerlo, T-SQL se queda corto en ciertas áreas, como el procesamiento intensivo de texto, operaciones matemáticas complejas o manipulación avanzada de estructuras de datos.

Ahora bien, que algo se pueda hacer no significa que deba hacerse. De hecho, habilitar CLR en una instancia es como abrir una puerta lateral en un castillo: puede estar bien si sabes quién entra y sale, pero si la dejas abierta, prepárate para las sorpresas.

Activar CLR: el primer pecado

Por defecto, CLR viene deshabilitado. Hay que habilitarlo explícitamente:

Hasta aquí todo bien. Pero luego llega la decisión importante: ¿qué nivel de seguridad vamos a aplicar? Aquí es donde muchos pecan por ignorancia o por prisa. SQL Server permite establecer niveles de seguridad en los ensamblados: SAFE, EXTERNAL_ACCESS y UNSAFE.

  • SAFE es la opción por defecto y la más limitada. Permite usar solo código seguro dentro del sandbox de SQL Server. Vamos, que no puedes acceder al sistema de archivos ni hacer llamadas a red.
  • EXTERNAL_ACCESS permite acceso al sistema, como archivos o recursos de red. Requiere permisos adicionales en la base de datos y en el servidor.
  • UNSAFE es exactamente lo que parece: la carta blanca para que alguien con tiempo libre y ganas de destruir te monte un agujero de seguridad monumental. Desde abrir un puerto TCP hasta modificar binarios en disco. Y si el servidor ejecuta SQL Server con permisos de sistema, el daño potencial es ilimitado.

El resumen es simple: si usas EXTERNAL_ACCESS o UNSAFE y no sabes exactamente lo que estás haciendo, ya puedes ir llamando al responsable de seguridad para una charla “interesante”.

¿Y por qué usar CLR? ¿Realmente aporta algo?

En ocasiones, sí. Hay escenarios donde CLR no solo tiene sentido, sino que puede ser la solución óptima. Por ejemplo:

  • Procesamiento de texto complejo: Las expresiones regulares en T-SQL son una promesa aún. Mientras que estamos empezando a verlas en las previews de Azure SQL y SQL Server 2025, en C# puedes usarlas sin volverte loco. Una función CLR que use Regex.Match puede reemplazar cientos de líneas de T-SQL feo y lento.
  • Cálculos matemáticos avanzados: ¿Has intentado hacer operaciones trigonométricas complejas o cálculos estadísticos avanzados con T-SQL? No, ¿verdad? Pues eso. CLR te permite usar librerías de .NET que hacen esto sin romperte la cabeza. Aunque, pensándolo bien, ¿no preferirías usar python o R para estas cosas?
  • Manejo de estructuras como arrays o diccionarios: Aunque con SQL Server 2022 y sus mejoras en JSON y XML estas necesidades han bajado, aún hay casos donde un buen Dictionary<string, List<int>> en C# resuelve en milisegundos lo que en T-SQL requeriría un máster.
  • Performance en funciones escalares: Una función escalar en T-SQL puede ser un veneno para el rendimiento si se ejecuta por cada fila de una tabla grande. En CLR, ese impacto puede reducirse significativamente.

Eso sí, si el único motivo para usar CLR es que “yo sé C# y no me gusta T-SQL”, lo que necesitas no es un CLR, es un cambio de trabajo.

Cómo crear un ensamblado CLR sin invocar a Satán

Veamos el proceso básico:

Primero escribes el código en C# (en Visual Studio o lo que uses). Luego compilas el ensamblado (DLL) y lo subes a SQL Server con CREATE ASSEMBLY. Por último creas la función o procedimiento que lo expone. Un ejemplo sencillo:

Código C#:

Compilas esto como DLL y lo subes:

Y voilà. Ya puedes usar dbo.ReverseString(‘hola’).

¿Dónde rompe CLR las cosas y por qué?

Ahora hablemos de los pecados originales del CLR. El primero es la dificultad de mantenimiento. El código está fuera del ámbito natural del DBA. Si mañana hay que parchear un bug en ese ensamblado, necesitas recompilar, volver a subirlo y cruzar los dedos para que no tengas dependencias rotas. Además, muchas veces ni siquiera se versionan correctamente. Y sí, lo hemos visto en producción. Más veces de las que nos gustaría.

Segundo, el tema de la seguridad. Un ensamblado con UNSAFE puede hacer cualquier cosa en el sistema operativo. Y cuando decimos cualquier cosa, es cualquier cosa. ¿Quieres que tu base de datos escanee el disco C:? Puedes hacerlo. ¿Deberías hacerlo? No. Nunca.

Tercero, el rendimiento. Aunque en algunos escenarios el CLR escala mejor que T-SQL (especialmente en funciones escalares y algoritmos complejos), introducir CLR sin medir su impacto real es como poner un turbo a un coche sin frenos. Necesitas pruebas serias, monitorización con Extended Events o Profiler, y entender bien los contextos de ejecución y memoria.

CLR y los entornos modernos

¿Sigue teniendo sentido usar CLR en 2025? Depende. SQL Server ha mejorado mucho en áreas como funciones en línea, JSON, XML, y hasta integración con Python y R vía Machine Learning Services. Muchas cosas que antes requerían CLR ahora se pueden hacer dentro del propio SQL Server sin salir del ecosistema T-SQL.

Pero aún hay casos de uso válidos: si necesitas lógica reutilizable, validaciones complejas, o consumir recursos externos con control, puede ser una herramienta útil. Eso sí, no abuses. Lo que empieza como una solución elegante puede acabar como un infierno de dependencias, DLLs perdidas y bugs imposibles de depurar.

Conclusión

El CLR no es el enemigo. El enemigo es usar herramientas que no entendemos solo porque «molan» o porque «así lo hacemos en .NET». SQL Server permite muchas cosas, pero no todas son recomendables. Si vas a meter CLR en tu arquitectura, hazlo con cabeza, documentación, versiones bien gestionadas y, por supuesto, tests.

Y si estás usando funciones CLR porque no sabías cómo hacerlo en T-SQL… mejor sigue leyendo el blog. Tenemos artículos sobre expresiones regulares y otras maravillas menos propensas a prenderle fuego a tu entorno productivo.

Porque sí, puedes meter C# en SQL Server, y también puedes meter un gato en una lavadora. Pero no deberías.

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, Rendimiento, SQL Server, 0 comentarios

LINQ y SQL Server: consulta tú, que ya lloro yo

Si eres developer y trabajas con .NET, lo más probable es que en algún momento hayas caído rendido ante LINQ. Lo entiendo, es tentador cual canto de sirena. Tiene lo suyo: expresividad, fluidez, integración total con C#… es cómodo. Pero si trabajas con SQL Server como motor de datos, más te vale saber qué está haciendo LINQ por detrás. Porque aquí, en las profundidades del motor, no nos tragamos la excusa de «yo sólo puse un .Where()».

Este artículo no es para demonizar LINQ. Es para que entiendas, desde el punto de vista del DBA que va a recibir tus consultas, por qué usar LINQ sin saber cómo funciona es como lanzar T-SQL con los ojos cerrados y esperar que salga rápido. Alerta spoiler: no lo hace.

LINQ: el ORM moderno que no siempre sabe lo que hace

A ti LINQ te permite escribir consultas con tipos, IntelliSense, lambdas y toda la fantasía moderna del desarrollo elegante. A nosotros nos llega una consulta SQL que ha generado un ORM sin ningún sentido del rendimiento, de los índices ni del plan de ejecución.

Tú ves:

Nosotros vemos: joins sin sentido, LEFT OUTER JOIN innecesarios, condiciones que no usan índices, y a veces hasta SELECT TOP 1000000 porque alguien no quería paginar como es debido. Lo que para ti es una consulta sencilla, para nosotros puede ser una fiesta de bloqueos, CPU y discos saturados.

LINQ no es malo. Pero no sabe optimizar. Y su traductor a SQL (ya sea EF, LINQ to SQL o el flavor de turno) sólo tiene una misión: funcionar. No rendir. Eso, amigos, es responsabilidad vuestra.

SQL Server no es una caja negra

Si has trabajado toda tu vida con Entity Framework sin mirar lo que genera por debajo, te estás perdiendo la mitad de la película. Y no la buena. SQL Server es un motor potente, flexible y bastante generoso… pero sólo cuando le das instrucciones claras.

Cuando llega una consulta LINQ traducida automáticamente, SQL Server hace lo que puede. Pero si la estructura de la consulta es compleja, el uso de joins es arbitrario y los filtros se aplican después del .ToList(), entonces no hay milagros. Hay “table scans”, “sort warnings”, “hash matches” no deseados y todo lo que un DBA no quiere ver en el plan de ejecución.

Lo que tú ves como un .Include() para cargar relaciones, nosotros lo vemos como una máquina de hacer JOINs sin control. Y cuando eso se hace en producción con datos reales, hay que estar muy seguro de que se entiende lo que se está mandando.

IEnumerable, IQueryable y el horror que no ves en LINQ

Hay un detalle que seguimos viendo incluso en equipos senior: no distinguir IEnumerable de IQueryable a la hora de consultar.

Cuando usas IQueryable, la consulta se traduce a SQL y se ejecuta en la base de datos. Bien. Pero cuando haces .ToList() antes de aplicar más filtros, estás trayendo todo a memoria y filtrando en .NET. Eso está bien si tienes diez registros. Si tienes diez millones, acabas de llenar el servidor de aplicaciones con datos que no necesitabas.

El ORM no sabe optimizar. Tú sí deberías.

Así que cuando alguien hace esto:

Y luego se pregunta por qué la aplicación va lenta, la respuesta es clara: no es la base de datos, es tu código. LINQ puede ser declarativo, pero no mágico.

Los casos en que LINQ hace llorar al optimizador

¿Sabes lo que pasa cuando encadenas tres .Include()s, haces un .SelectMany() y luego aplicas una condición que sólo puede evaluarse en cliente? Que el SQL resultante se convierte en un engendro. Y a nosotros nos llega una consulta de 150 líneas con subconsultas, columnas que no se usan y JOINs en cascada que anulan los índices.

Esto no es una exageración. Lo he visto con mis propios ojos. Y no una vez. Las herramientas modernas de desarrollo facilitan mucho la creación de consultas… que nadie ha revisado. Y luego cuando llegan las quejas de rendimiento, el culpable nunca es el ORM. Siempre es “la base de datos que no escala”.

No. La base de datos escala. Lo que no escala es traducir expresiones lambda como si fueran instrucciones optimizadas para un motor relacional.

Cómo hacer las paces: consejos para developers (sí, es por vuestro bien)

Lo primero: aprende a ver el SQL que estás generando. EF Core permite interceptar y registrar el SQL generado. Úsalo. Lee ese SQL. Míralo con ojos críticos. Si ves que tiene 10 joins, 30 columnas innecesarias y ninguna cláusula WHERE, no lo envíes a producción. Mándalo al infierno de staging, a ver cuánto tarda.

Segundo: si una consulta es crítica, escribe SQL tú mismo. Usa FromSqlRaw() o, mejor aún,  procedimientos almacenados dentro de SQL Server. No pasa nada. No es “menos elegante”, es más responsable.

Tercero: si tienes dudas, habla con el DBA. No somos ogros. Bueno, algunos sí. Pero en general, preferimos una conversación a tener que cazar queries con el SQL Profiler porque ha saltado una incidencia de rendimiento a las 3 de la mañana.

Cuarto: entiende el modelo de ejecución diferida de LINQ. Y si tienes que filtrar, hazlo antes del .ToList(). Siempre.

Y por último: no abuses del azúcar sintáctico. LINQ puede ser cómodo, pero no sustituye al conocimiento. No saber lo que está pasando en SQL Server es como conducir un coche sin saber si vas en primera o en tercera.

¿Cuándo sí usar LINQ?

LINQ brilla en consultas simples. En operaciones sobre colecciones en memoria. En proyecciones pequeñas y bien definidas. Si tu consulta es trivial y los datos están bien indexados, no hay problema. Pero si estás construyendo un informe complejo, una API con cientos de miles de registros o una consulta crítica para el negocio, deja de lado la comodidad.

Y si usas LINQPad, mejor. Ahí sí puedes ver lo que pasa y ajustar con cabeza. Porque en el fondo, LINQ no es el problema. El problema es no saber cuándo dejar de usarlo.

Conclusión: hay vida más allá de LINQ

No hay escapatoria. Si trabajas en un proyecto con SQL Server, y usas un ORM o LINQ, el rendimiento de tus consultas depende de ti. No vale decir “eso es cosa del DBA” o “es que eso lo hace el ORM solo”. Porque cuando la aplicación se arrastra, da igual de quién sea la culpa: hay que arreglarlo y ni todo el hardware del mundo arregla depende que consultas.

Así que la próxima vez que escribas una consulta LINQ, pregúntate: ¿qué SQL va a salir de aquí? ¿Lo he mirado? ¿Lo he probado con datos reales? ¿Estoy filtrando bien? ¿Estoy trayendo sólo lo que necesito?

Y si no puedes responder a eso con seguridad, tal vez lo mejor sea bajarse al T-SQL y escribir como los mayores.

Porque aquí no se trata de gustos. Se trata de no matar al servidor con buenas intenciones y malas queries.

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, Rendimiento, SQL Server, 0 comentarios

Antivirus en servidores SQL Server: ni sin él, ni contra él

Cuando hablamos de proteger un entorno SQL Server, lo primero que nos viene a la cabeza suelen ser los backups, la alta disponibilidad, los roles de seguridad, incluso el cifrado. Pero curiosamente, uno de los elementos más básicos de la protección –el antivirus– sigue siendo tratado como un accesorio molesto, o lo que es peor, como una amenaza potencial para el rendimiento. Y claro, en ese dilema entre seguridad y rendimiento, muchas veces se acaba apostando por el camino más cómodo: desactivar o ignorar.
No vamos a defender aquí que el antivirus es la piedra angular de la seguridad (no lo es), pero tampoco podemos dejar que el miedo a una caída de rendimiento justifique decisiones negligentes. Lo que necesitamos es un enfoque racional: configurar el antivirus de forma que proteja sin interferir con la operación normal del motor de base de datos. *Spoiler*: se puede. Lo difícil es que alguien lo haga bien.

¿Qué hace el antivirus y por qué molesta (si lo dejamos)?

El trabajo del antivirus es inspeccionar archivos y procesos en busca de patrones maliciosos. Nada nuevo. Lo que sí suele pasarse por alto es *cómo* lo hace: analiza archivos al abrirse, al modificarse, y en ocasiones al cerrarse. También puede inspeccionar procesos en memoria, acceder a redes compartidas, lanzar escaneos programados… y todo eso suena bastante normal hasta que lo dejas funcionando en un servidor SQL Server con una carga seria.
Porque claro, cuando el antivirus decide inspeccionar `tempdb.mdf` mientras el motor está haciendo un `HASH JOIN` como si no hubiera un mañana, empieza la fiesta: bloqueos, ralentizaciones, timeouts y, si tenemos suerte, algún bonito `IO_COMPLETION` en el error log. Y no olvidemos el momento en que el antivirus escanea los archivos `.ldf` en pleno `checkpoint`. Porque sí, todo archivo es sospechoso, incluso los que cambian cien veces por segundo.
El problema no es el antivirus. El problema es no configurarlo.

Archivos y procesos que debemos excluir

Vamos al grano. Hay una serie de rutas, extensiones y procesos que deben quedar fuera del radar del antivirus si queremos mantener la estabilidad y el rendimiento del servidor SQL Server. No es opcional. No es debatible. Está documentado por Microsoft, y si no lo aplicas, estás jugando a la ruleta rusa con discos girando.
Empezamos por los archivos:
– Las bases de datos, claro: archivos `.mdf`, `.ndf` y `.ldf`. Eso incluye todas las rutas donde se ubiquen, incluyendo `tempdb`. Da igual si están en discos SSD, en cabinas SAN o en la nube: fuera del antivirus.
– Los archivos de backup: `.bak`, `.trn`, `.sqb`, `.dif`, etc. Si el antivirus decide escanear un `.bak` de 500 GB en pleno restore, luego no nos preguntemos por qué tarda 2 horas más de lo previsto.
– Los archivos de trace, log y dumps generados por SQL Server (`.xel`, `.trc`, `.dmp`). Algunos de ellos se escriben en caliente, y otros pueden ser enormes.
– Las rutas de instalación de SQL Server y sus binarios (`sqlservr.exe`, `sqlagent.exe`, etc.). Los procesos de SQL no deberían ser intervenidos por el antivirus, especialmente durante eventos como reinicios o cambios de configuración.
También conviene excluir herramientas relacionadas que trabajen cerca del metal, como agentes de backup de terceros (Veeam, Commvault, etc.), soluciones de monitorización, y carpetas temporales utilizadas en procesos ETL, especialmente si están en el mismo servidor.
Y por supuesto: nada de escaneos programados sobre las unidades donde residen estos archivos. ¿Queremos una caída de rendimiento nocturna «espontánea»? Pues eso.

El antivirus SI tiene cabida en un servidor SQL

Llegados a este punto, alguien siempre pregunta: «¿Y si directamente no pongo antivirus?». Bueno, también puedes dejar la puerta del CPD abierta por si alguien quiere entrar a ver qué hay. Todo es cuestión de prioridades.
Un servidor SQL Server, aunque no navegue por Internet, puede estar expuesto a amenazas reales. Las vías de entrada son múltiples: conexiones RDP inseguras, accesos compartidos, usuarios que suben archivos, integraciones con sistemas vulnerables, incluso malwares que viajan dentro de backups. Y no olvidemos el ransomware, que no necesita un navegador para hacer su trabajo. Lo único que necesita es una puerta entreabierta.
Así que sí, el antivirus es necesario. Pero como con cualquier otra herramienta, hay que usarla con cabeza.

Buenas prácticas de configuración del antivirus

Ya sabemos qué excluir, pero ¿qué más podemos hacer para convivir con el antivirus sin odiarlo?
Lo primero, usar soluciones corporativas que permitan configurar políticas centralizadas. Nada de versiones domésticas ni «freemium» que prometen IA mágica y acaban con el 30% de tu CPU. Necesitamos un antivirus que permita gestionar exclusiones, programar escaneos fuera del horario de carga, y monitorizar actividad sin interferir con los procesos críticos.
Lo segundo, documentar las exclusiones. Es increíble la cantidad de veces que se instala un nuevo motor, se configura bien, y meses después alguien cambia el antivirus o actualiza políticas sin revisar nada. Resultado, el rendimiento se degrada, y nadie sabe por qué. Tener un documento claro con las rutas y configuraciones esenciales debería ser parte del estándar de despliegue.
Tercero, coordinar con el equipo de seguridad. Sí, esos que mandan correos con gráficos de amenazas que nadie entiende. Hablad con ellos. Enseñadles los KB de Microsoft. Explicad por qué hay que excluir ciertos archivos. Si lo hacéis bien, es posible que incluso lo agradezcan. Al final, todos queremos lo mismo, que el sistema esté seguro y funcione bien.
Y por último, revisar con cada parche o actualización. Las rutas cambian, las versiones cambian, y lo que funcionaba en SQL Server 2016 puede no ser igual en 2022. La tecnología cambia más deprisa que las excusas para no leer la documentación.

Recursos recomendados (y válidos)

Si alguien necesitas pruebas documentales para convencer al comité de seguridad, que tire de lo oficial. Microsoft tiene un artículo actualizado con las exclusiones recomendadas para SQL Server que puedes encontrar aquí.
También hay buenas prácticas en la documentación de cada solución antivirus empresarial. Lo importante es no quedarse en lo básico: si hay un `.exe` que escanea un `.mdf`, eso es un problema.

Conclusión

El antivirus en un servidor SQL Server no es el enemigo. El enemigo es la ignorancia operativa. Dejar un antivirus sin configurar es tan irresponsable como dejar que todo el mundo sea `sysadmin`. Y no, no exageramos.
Podemos tener rendimiento y seguridad. Pero como casi todo en esta vida (excepto los planes de licenciamiento de Microsoft), requiere esfuerzo. Excluir bien, revisar políticas, hablar entre equipos y no asumir que las cosas «ya vienen bien puestas».
Si seguimos confiando en que el antivirus «sabrá lo que hace», luego no nos sorprendamos si decide escanear `master.mdf` en mitad de un failover. Porque ya lo hemos dicho antes: el antivirus no es malo. Lo malo es dejarlo pensar por sí mismo.
Y no, no vamos a acabar este artículo diciendo que “la seguridad empieza por uno mismo”. La seguridad empieza por no meter la pata.

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 Rendimiento, SQL Server, 1 comentario

Introduccion a Service Broker

Hay funcionalidades de SQL Server que llevan años en la sombra, casi como reliquias de una época en la que creíamos que todo tenía que resolverse con triggers, cursores o, peor aún, con consultas agresivas cada 30 segundos. Entre esas joyas ignoradas, olvidadas o simplemente mal comprendidas, destaca Service Broker. No es nuevo, no es trendy y desde luego no tiene marketing detrás, pero cuando se entiende y se usa bien, hace cosas que ni Kafka ni RabbitMQ pueden soñar… al menos no sin invitar a medio equipo de infraestructura.

¿Qué es Service Broker y por qué sigue vivo?

Service Broker es el subsistema de mensajería nativo de SQL Server. Está ahí desde la versión 2005, y a diferencia de muchas promesas de aquella época, sigue funcionando sin necesidad de parches ni microservicios externos. La idea es sencilla: permitir que dos o más bases de datos se comuniquen de forma asíncrona, fiable, y con garantías de entrega, utilizando colas, mensajes y contratos definidos dentro del propio motor.

¿Parece demasiado bonito? Lo es, si lo usas bien. Pero claro, como todo en SQL Server, si lo activas sin entenderlo, puede acabar como aquel CLR que alguien dejó en producción y que ahora nadie se atreve a tocar.

Mensajes, contratos y colas: bienvenidos al lenguaje de Service Broker

La arquitectura de Service Broker se apoya en tres conceptos fundamentales: mensajes, contratos y colas.

Los mensajes son lo que uno espera: trozos de datos enviados de un punto a otro. Pueden llevar XML, texto, JSON, o lo que se te ocurra empaquetar mientras quepa en varbinary(max). Pero cuidado: si lo que mandas es basura, lo que recibes es basura asincrónica.

Los contratos definen qué tipos de mensajes se pueden intercambiar y quién puede enviarlos. Un contrato no es un capricho burocrático, es la única barrera entre un sistema robusto y un caos de mensajes huérfanos flotando en una cola sin dueño.

Las colas, por su parte, son objetos de base de datos donde se almacenan los mensajes. Pueden estar enlazadas a una tabla de activación, y de ahí lanzar procedimientos almacenados automáticamente cuando llega un mensaje. Y aquí es donde empiezan las posibilidades interesantes: procesamiento asíncrono, desacoplamiento lógico, incluso orquestación de tareas complejas sin necesidad de middleware externo.

Activación interna: el punto en el que Service Broker se vuelve interesante

Service Broker permite asociar una cola a un procedimiento almacenado. Cuando llegan mensajes nuevos, SQL Server puede lanzar automáticamente una instancia de ese procedimiento. Esto permite procesar eventos en tiempo real (bueno, en SQL-real) sin que el cliente tenga que esperar.

¿Significa esto que podemos hacer procesamiento paralelo dentro de SQL Server sin tocar SSIS, sin montar microservicios ni invocar APIs REST desde T-SQL? Exactamente. Con matices, claro. Esto no es Node.js, ni pretende serlo. Pero si lo que necesitas es procesar tareas, disparar workflows o mover datos entre bases, sin acoplar todo a una única transacción gigantesca, entonces estás en el lugar adecuado.

Un ejemplo real: procesar tareas sin freír el servidor

Supongamos que tenemos un sistema que necesita registrar operaciones de auditoría cada vez que un usuario modifica un registro crítico. Antes de que alguien proponga otro trigger que escriba directamente en la tabla de auditoría (y ralentice la operación principal), pensemos con más cabeza: ¿por qué no delegar esa escritura a un sistema asincrónico?

Con Service Broker, podemos:

  • Crear un mensaje con la información de auditoría.
  • Enviarlo a una cola específica.
  • Tener un procedimiento almacenado escuchando esa cola.
  • Procesar el mensaje y escribir la auditoría sin interferir con la transacción principal.

Esto no sólo mejora el rendimiento, sino que evita que un fallo en la auditoría reviente una operación de negocio. Sí, lo sabemos: no siempre se puede hacer, pero cuando se puede, es una diferencia de nivel.

Seguridad, rutas y conversación: lo que hay que saber de service Broker

Service Broker funciona bajo un modelo de «conversaciones» entre servicios. Sí, como las conversaciones de WhatsApp, pero con contratos y sin stickers. Estas conversaciones son bidireccionales y tienen estado, lo que permite enviar múltiples mensajes en ambos sentidos de forma controlada.

Para que todo esto funcione entre diferentes bases de datos o instancias, necesitamos definir servicios, rutas, y a veces, incluso certificados si vamos a cruzar servidores. Aquí es donde muchos tiran la toalla, porque no es trivial y la documentación oficial tiene ese encanto críptico tan característico de Microsoft cuando parece que no quiere que usemos algo.

Pero si superamos esta curva de aprendizaje pronunciada, podremos tener mensajería distribuida transaccional entre instancias SQL Server sin necesidad de colas externas, sin middleware y sin lag de 10 segundos.

¿Por qué no se usa más?

Primero, porque no está de moda. Service Broker no tiene un logo molón, ni una conferencia anual, ni camisetas con chistes de desarrolladores de colas. Pero eso no lo hace menos útil. Segundo, porque requiere entender T-SQL más allá del SELECT con JOIN, y eso ya elimina a cierto porcentaje de desarrolladores.

Y tercero, porque no se ha enseñado bien. Muchos lo han visto como «algo para entornos muy específicos», sin darse cuenta de que puede resolver problemas cotidianos: colas de trabajo, asincronía controlada, desacoplamiento entre componentes de una aplicación.

Cuándo usar Service Broker y cuándo no

No todo es un clavo, y Service Broker no es un martillo universal. Si necesitas alta escalabilidad, integración con sistemas heterogéneos o necesitas exponer eventos a sistemas no-SQL, probablemente debas mirar soluciones como Azure Service Bus, RabbitMQ o Kafka.

Pero si tu universo es SQL Server, si el cuello de botella está en las transacciones, y si quieres que los procesos se comuniquen entre sí dentro del mismo motor, entonces estás dejando pasar una oportunidad de oro si ignoras Service Broker.

Conclusión

Service Broker no es una bala de plata, ni pretende serlo. Pero es una de esas funcionalidades que, bien comprendidas, permiten diseñar soluciones limpias, escalables y robustas sin necesidad de inventarse una arquitectura de microservicios para cada cosa.

Y lo mejor es que ya está en tu SQL Server. Solo tienes que activarlo, entenderlo, y no cometer los errores de quienes pensaron que podían usarlo sin leer el manual. Porque sí, hay que cerrar las conversaciones, y no, no es opcional. Como dejar un BEGIN CONVERSATION sin END CONVERSATION: es como dejar una transacción abierta esperando que alguien más la cierre. O que venga un unicornio.

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, Rendimiento, SQL Server, 0 comentarios

Tablas temporales vs. variables de tabla en SQL Server: diferencias que sí importan

Las tablas temporales y las variables de tabla en SQL Server son como los destornilladores y las llaves inglesas de nuestra caja de herramientas: parecidas en concepto, pero con propósitos, comportamientos y peculiaridades bien distintos. Ambos mecanismos nos permiten trabajar con conjuntos de datos intermedios sin necesidad de recurrir a tablas permanentes, pero quien haya intentado usarlos indistintamente en entornos reales sabe que las diferencias importan. Y mucho.

Vamos a destripar ambas opciones con calma, ver cómo se comportan, qué ventajas ofrecen y en qué situaciones conviene (o no) usarlas. Porque sí, aunque parezcan intercambiables, tratarlas como tal suele acabar en consultas lentas, planes de ejecución misteriosos y DBA rezando en voz baja.

Tablas temporales: las inquilinas del tempdb

Las tablas temporales (#TablasTemporales) son lo más parecido a una tabla normal que podemos crear en tiempo de ejecución sin dejar huella permanente. Se crean en la base de datos tempdb, y existen mientras dure la sesión (o el scope) que las creó. Podemos definir índices, claves primarias, restricciones, estadísticas… Vamos, que son tablas de verdad, aunque vivan en alquiler. Su sintaxis es familiar y directa:

O incluso más común aún:

El plan de ejecución que las acompaña suele ser robusto, especialmente si trabajamos con cantidades de datos considerables. SQL Server genera estadísticas automáticas sobre las columnas, lo cual permite un plan más ajustado al volumen real. Esto, que parece un detalle técnico sin importancia, marca la diferencia entre una consulta que vuela y otra que arrastra los pies como yo un lunes por la mañana antes del primer café.

¿Inconvenientes? Algunos. El uso de tempdb implica competencia con otros procesos que también están abusando del mismo recurso. Además, su ciclo de vida depende del contexto: si las creamos en un procedimiento almacenado y lo llamamos varias veces, conviene borrarlas explícitamente con DROP TABLE o usar IF OBJECT_ID(…) IS NOT NULL.

Y por supuesto, cuidado con el scope: una tabla temporal creada dentro de un procedimiento no es accesible desde fuera. Pero eso no debería sorprendernos. Tampoco esperamos que un DECLARE de una variable sobreviva al final del procedimiento.

Variables de tabla: pequeñas, rápidas… y caprichosas

Las variables de tabla (@VariablesDeTabla) se introdujeron como una forma rápida y elegante de manejar pequeños conjuntos de datos sin el overhead de una tabla temporal completa. Son ideales para almacenar unas cuantas filas, iterar lógicamente sobre ellas o devolver resultados simples.

La sintaxis es limpia:

Y su ciclo de vida es exactamente el del bloque donde se declaran. No hay que preocuparse por borrarlas ni por interferencias externas. Hasta aquí todo bien.

Ahora viene el problema: SQL Server no genera estadísticas sobre variables de tabla. Nunca. Ni en 2008 ni en 2022. Esto significa que el optimizador trabaja a ciegas. Literalmente: asume que una variable de tabla tiene una única fila. Da igual si tiene 1 o 10.000. El plan de ejecución será el de una tabla de una fila. Y eso, amigos, rara vez termina bien.

¿Hay excepciones? Desde SQL Server 2019, con OPTION (RECOMPILE), el optimizador puede estimar el número real de filas en algunos casos. Pero es una tirita en una fractura abierta. A veces ayuda, otras no. Y seguir usándolas a ciegas es una receta para la frustración.

¿Entonces son inútiles? No, ni mucho menos. Funcionan de maravilla cuando el número de filas es pequeño (menos de 100 suele ser seguro) y cuando las operaciones son simples. Pero si metemos un JOIN, un GROUP BY o empezamos a empujar lógica compleja… mejor sacar la artillería de verdad: tabla temporal.

Tablas temorales vs Variables de tabla: lo que no te dice la documentación

Hablemos claro. Las diferencias no están solo en la sintaxis o el ámbito. Lo importante es cómo se comportan bajo carga, cómo afectan al plan de ejecución y qué tipo de mantenimiento requieren. Para compararlas vamos a ver uno a uno los aspectos más interesantes.

Estadísticas

Las tablas temporales sí generan estadísticas; las variables de tabla no. Esto significa que las temporales permiten planes de ejecución más óptimos en escenarios con muchos datos. Las variables, no.

Soporte de índices

Ambas opciones permiten claves primarias y restricciones únicas. Desde SQL Server 2014 es posible definir índices secundarios en variables de tabla, pero solo dentro de la declaración y con sintaxis limitada. En tablas temporales podemos crear cualquier tipo de índice, incluidos los columnstore, sin restricciones adicionales.

Transacciones

Las variables de tabla no se ven afectadas por ROLLBACK. Si algo falla, su contenido sigue ahí, lo cual puede ser bueno… o un bug encubierto. Las tablas temporales, en cambio, participan en las transacciones como cualquier otra tabla.

Almacenamiento y persistencia

Ambas opciones viven en tempdb, aunque las variables lo hagan de forma menos visible. Pero a nivel físico, no hay magia: no están «en memoria», como algunos aún creen. Eso sí, las temporales suelen dejar más rastro en el sistema de archivos si no se gestionan bien.

Además, como ya hemos comentado las tablas temporales duran lo que dura la sesión, es decir, mientras no las borremos o cerremos esa sesión seguirán ahí. Las variables tipo tabla, por el contrario duran lo que dura la ejecución del lote (batch).

Si veis en la imagen, la segunda consulta no encuentra nada. Esto pasa porque después del “GO” ya se considera otro lote.

Paralelismo

Las tablas temporales pueden beneficiarse del paralelismo en las consultas; las variables de tabla, salvo casos contados y versiones muy recientes de SQL Server, no.

Lectura y escritura

En escenarios de alto volumen, las operaciones sobre variables de tabla pueden ser considerablemente más lentas que sobre tablas temporales. Aunque el coste del DECLARE parezca nulo, el impacto acumulado en los planes de ejecución mal optimizados se paga caro.

Casos de uso, ¿Cuándo elegir tablas temporales o variables de tabla?

Una variable de tabla nos viene de perlas cuando queremos devolver una pequeña tabla desde una función, cuando estamos en mitad de un script complejo que necesita guardar una docena de valores intermedios, o cuando buscamos claridad sin sacrificar rendimiento (porque sabemos que los datos son pocos y controlados). Por norma general, no deberíamos usarlas para más de 100 registros.

Una tabla temporal brilla en todo lo demás: cargas intermedias, transformaciones complejas, conjuntos de datos que van a vivir varias etapas, o cuando necesitamos analizar y refinar el rendimiento de una consulta. Incluso para esas subconsultas que usamos varias veces en una misma consulta y pueden llegar a ser pesadas.

También conviene recordar que hay un tercer actor en esta historia: las tablas temporales globales (##TablaGlobal) y las tablas de memoria (MEMORY_OPTIMIZED). Pero eso ya es otro capítulo. O varios.

Conclusión

Elegir entre una tabla temporal y una variable de tabla no debería depender del estado de ánimo, sino del uso que le vamos a dar. Si los datos son escasos, la lógica es sencilla y no necesitamos estadísticas ni índices complejos, la variable funciona. Pero si hay que unirse a otras tablas, mover volumen o exprimir rendimiento, la tabla temporal es la opción profesional.

No olvidemos que el optimizador de SQL Server toma decisiones basadas en lo que sabe. Y con una variable de tabla, lo que sabe es poco. Si le damos una tabla temporal bien definida, con índices y estadísticas, puede hacer su trabajo. Si le damos una caja cerrada con un «ya te apañarás», no esperemos milagros.

Así que, la próxima vez que tengamos que elegir entre DECLARE @Tabla y CREATE TABLE #Temp, pensemos dos veces. Porque sí, ambas pueden almacenar datos. Pero sólo una de ellas está preparada para aguantar una jornada completa sin pedir un café doble a mitad de camino. Y no, no es la variable.

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, Rendimiento, SQL Server, 1 comentario

Tarea de Limpieza Fantasma en SQL Server

En nuestro día a día como profesionales de SQL Server, nos enfrentamos a una multitud de procesos internos que trabajan silenciosamente para mantener nuestras bases de datos saludables y con un rendimiento óptimo. Uno de estos procesos, a menudo menos visible pero de vital importancia, es la tarea de limpieza fantasma. En este artículo, vamos a sumergirnos en las profundidades de este mecanismo, explorando su funcionamiento interno, su necesidad y las consideraciones clave para su gestión.

¿Qué son los Registros Fantasma y por qué aparecen en SQL Server?

Cuando eliminamos registros de una tabla que tiene un índice (ya sea clúster o no clúster), SQL Server no los borra físicamente de inmediato de las páginas de datos. En su lugar, el registro se marca internamente como «para ser eliminado», un estado que conocemos como registro fantasma «ghosted». Imaginemos una biblioteca donde, en lugar de retirar un libro inmediatamente de la estantería, simplemente le ponemos una etiqueta que dice «para retirar». El libro sigue allí, ocupando espacio, pero marcado para una acción posterior.

Esta técnica de «eliminación lógica» ofrece varias ventajas significativas en términos de rendimiento durante las operaciones de borrado. Primero, la operación de eliminación en sí misma se vuelve mucho más rápida, ya que solo implica cambiar un bit en la cabecera del registro en lugar de una manipulación física de los datos. Segundo, las operaciones de reversión (rollback) también se optimizan, ya que simplemente necesitamos desmarcar los registros como «para eliminar» en lugar de tener que reinsertar los datos borrados.

Además de la optimización del rendimiento en las operaciones DML, los registros fantasma son esenciales para otras funcionalidades importantes de SQL Server. Son un componente clave para el bloqueo a nivel de fila, permitiendo que diferentes transacciones operen concurrentemente en distintas filas de la misma página sin interferir entre sí. También son fundamentales para el aislamiento de instantánea, donde necesitamos mantener versiones anteriores de las filas para garantizar una vista consistente de los datos para las transacciones en curso.

El Proceso de Limpieza Fantasma

Una vez que una eliminación se ha confirmado (commit), entra la tarea de limpieza fantasma. Este es un proceso en segundo plano, monohilo, que se encarga de eliminar físicamente los registros que han sido marcados como fantasmas. Podemos pensar en este proceso como el personal de la biblioteca que, periódicamente, revisa los libros con la etiqueta «para retirar» y los saca físicamente de las estanterías, liberando espacio.

La tarea de limpieza fantasma se ejecuta automáticamente en intervalos regulares: cada 5 segundos para SQL Server 2012 y versiones posteriores, y cada 10 segundos para SQL Server 2008 y 2008 R2. En cada activación, el proceso verifica si alguna base de datos ha sido marcada como que contiene registros fantasma. Si encuentra alguna, escanea las páginas PFS de esa base de datos en busca de páginas que contengan registros fantasma. Al encontrarlas, procede a eliminar físicamente los registros marcados, con un límite de 10 páginas procesadas por cada ejecución. Esta limitación asegura que el proceso de limpieza no consuma excesivos recursos del sistema en un solo ciclo.

Es importante destacar que la tarea de limpieza fantasma opera a nivel de base de datos. Cuando una página contiene registros fantasma, la base de datos a la que pertenece se marca internamente. El proceso de limpieza solo escaneará aquellas bases de datos que estén marcadas de esta manera. Una vez que todos los registros fantasma de una base de datos han sido eliminados, la base de datos se marca como «sin registros fantasma», y el proceso la omitirá en sus siguientes ejecuciones. Además, si la tarea de limpieza no puede obtener un bloqueo compartido en una base de datos (por ejemplo, si otra operación está utilizando la base de datos de forma exclusiva), la omitirá y volverá a intentarlo en su próxima ejecución.

¿Por qué son importantes los Registros Fantasma?

Como mencionaba anteriormente, la existencia de los registros fantasma no es arbitraria; responde a necesidades fundamentales del motor de base de datos. La optimización del rendimiento de las operaciones de eliminación es una de las principales razones. Al realizar una eliminación lógica inicial, SQL Server minimiza la sobrecarga inmediata, permitiendo que las transacciones se completen más rápidamente. Esto es crucial en sistemas con una alta tasa de operaciones DML.

La necesidad de bloqueo a nivel de fila también juega un papel crucial. Los registros fantasma permiten que el motor de base de datos mantenga la coherencia y el aislamiento entre transacciones concurrentes que podrían estar interactuando con la misma página de datos.

Finalmente, en entornos donde el aislamiento de instantánea está habilitado, los registros fantasma son esenciales para mantener las versiones anteriores de las filas. Esto garantiza que las transacciones que se iniciaron antes de la operación de eliminación puedan seguir viendo una imagen consistente de los datos en el momento en que se iniciaron.

Monitorizando la actividad de Limpieza Fantasma y la presencia de Registros Fantasma

Aunque la tarea de limpieza fantasma opera en segundo plano, es posible monitorizar su actividad y la presencia de registros fantasma en nuestras bases de datos. Podemos utilizar la DMV `sys.dm_exec_requests` para identificar si el proceso de limpieza fantasma está en ejecución, buscando peticiones con un comando similar a ‘%ghost%’.

Para determinar cuántos registros fantasma existen en una base de datos específica, podemos utilizar la DMV `sys.dm_db_index_physical_stats`. La siguiente consulta nos proporciona un resumen del número total de registros fantasma por base de datos:

Esta información puede ser valiosa para identificar bases de datos con una acumulación significativa de registros fantasma, lo que podría indicar una alta actividad de eliminación o posibles problemas con el proceso de limpieza.

Deshabilitar la Limpieza Fantasma: ¿Cuándo y cuáles son las consecuencias?

En sistemas con una carga de trabajo muy alta de eliminaciones, se puede deshabilitar la tarea de limpieza fantasma utilizando el trace flag 661. La justificación detrás de esto es que, en algunos escenarios extremos, el proceso de limpieza podría no ser capaz de mantenerse al día con el ritmo de las eliminaciones, lo que podría llevar a problemas de rendimiento al mantener páginas en el buffer pool y generar E/S.

Sin embargo, es crucial entender las graves implicaciones de deshabilitar la limpieza fantasma. Al hacerlo, los registros marcados para eliminación permanecerán en las páginas indefinidamente, impidiendo que SQL Server reutilice ese espacio. Esto puede llevar a un crecimiento innecesario del tamaño de la base de datos («bloated database files») y a problemas de rendimiento. La falta de reutilización de espacio obliga a SQL Server a agregar datos a páginas nuevas, lo que puede aumentar la frecuencia de divisiones de página («page splits»). Las divisiones de página son operaciones costosas que pueden afectar negativamente el rendimiento de las consultas y la creación de planes de ejecución.

Compensar la no limpieza automática

Si decidimos deshabilitar la tarea de limpieza fantasma (una acción generalmente no recomendada y que debe probarse exhaustivamente en un entorno controlado antes de implementarse en producción), necesitaremos tomar acciones alternativas para eliminar los registros fantasma y reclamar el espacio. Algunas opciones son:

  • Reconstrucción de índices: Esta operación mueve los datos alrededor de las páginas, eliminando los registros fantasma en el proceso.
  • Ejecución manual de `sp_clean_db_free_space`: Este procedimiento almacenado limpia los registros fantasma de todas las páginas de datos de una base de datos.
  • Ejecución manual de `sp_clean_db_file_free_space`: Similar al anterior, pero permite limpiar los registros fantasma de un archivo de datos específico.

Es fundamental recordar que deshabilitar la tarea de limpieza fantasma sin implementar una estrategia de limpieza alternativa conducirá inevitablemente a problemas de rendimiento y un crecimiento descontrolado de la base de datos.

Registros Fantasma en Heaps

Es interesante notar que, durante el procesamiento normal, los registros fantasma no ocurren en las tablas HEAP. La razón principal es que estas tablas no tienen una estructura de índice que requiera el mantenimiento de eliminaciones lógicas para la coherencia de la estructura. Cuando se elimina un registro de una tabla HEAP, se elimina físicamente de la página.

Sin embargo, existe una excepción importante a esta regla: cuando el aislamiento de instantánea está habilitado, las eliminaciones de un HEAP sí generan registros fantasma como parte del proceso general de control de versiones. Esto puede tener efectos secundarios interesantes. Por ejemplo, un registro con control de versiones tiene una sobrecarga adicional de 14 bytes. Si un registro de un HEAP se convierte repentinamente en un registro con control de versiones debido a una eliminación bajo aislamiento de instantánea, su tamaño puede aumentar en 14 bytes, lo que podría hacer que ya no quepa en la página actual. Esto, en casos donde la página está llena, podría llevar a que el registro se mueva, resultando en un par de registros de reenvío/reenviado, solo por haber sido eliminado.

La Limpieza Fantasma y su impacto en el Log de transacciones

Las operaciones relacionadas con los registros fantasma también se reflejan en el registro de transacciones. Cuando se elimina un registro de una página de índice, la operación se registra con un contexto de «ghosting» (LCX_MARK_AS_GHOST). Además, la modificación de la página PFS para indicar la presencia de registros fantasma también se registra.

Cuando la tarea de limpieza fantasma se activa y elimina físicamente los registros fantasma, esta acción también se registra en el log de transacciones como una operación de LOP_EXPUNGE_ROWS. Podemos observar esta actividad en el log de transacciones utilizando la función fn_dblog().

Consideraciones de rendimiento

En sistemas con un volumen extremadamente alto de operaciones de eliminación, es posible que la tarea de limpieza fantasma no pueda mantenerse al día con el ritmo de generación de registros fantasma. Dado que es un proceso monohilo, en servidores con muchos núcleos y una alta concurrencia de eliminaciones, un solo hilo dedicado a la limpieza podría convertirse en un cuello de botella.

Cuando la tarea de limpieza fantasma se retrasa significativamente, puede generar problemas de rendimiento al mantener páginas con registros eliminados en el buffer pool, consumiendo memoria que podría ser utilizada por datos activos. También puede generar operaciones de E/S al acceder a estas páginas para realizar la limpieza. En algunos casos, esto puede llevar a un aumento en el uso de CPU y a una degradación general del rendimiento del sistema.

Alternativas en escenarios de alta carga

Ante escenarios donde la tarea de limpieza fantasma por defecto no es suficiente, existen algunas alternativas a considerar (siempre con precaución y una comprensión clara de sus implicaciones):

  • Deshabilitar temporalmente la limpieza fantasma y realizar mantenimiento de índices de forma más agresiva: Como decía antes, esto debe hacerse con extremo cuidado y requiere una estrategia clara para reclamar el espacio.
  • Utilizar DBCC FORCEGHOSTCLEANUP: Este comando no documentado fuerza la limpieza de todos los registros fantasma de una base de datos. Aunque puede ser útil en situaciones de emergencia para reclamar espacio rápidamente, su naturaleza no documentada implica que su comportamiento y posibles efectos secundarios no están garantizados y podrían cambiar en futuras versiones de SQL Server. Su uso debe ser evaluado y probado cuidadosamente en entornos no productivos. Yo personalmente no lo recomiendo.
  • Ajustar el diseño de la base de datos y los patrones de acceso: En algunos casos, repensar la forma en que se realizan las eliminaciones o la estructura de los índices podría ayudar a mitigar la acumulación de registros fantasma. Por ejemplo, en tablas con altas tasas de eliminación e inserción en las mismas áreas del índice, podría ser beneficioso considerar estrategias de particionamiento.
  • Forzar la activación de la limpieza fantasma: Se dice, aunque no es una solución oficial, que realizar un escaneo completo de un índice (por ejemplo, con un SELECT COUNT(*)) puede forzar a que las páginas con registros fantasma se añadan a la cola de la tarea de limpieza para su posterior procesamiento.

Conclusión

La tarea de limpieza fantasma es un componente esencial del motor de base de datos de SQL Server que trabaja discretamente para asegurar que el espacio ocupado por los registros eliminados se recupere y que el rendimiento general no se vea afectado negativamente. Aunque en situaciones excepcionales se puede considerar su deshabilitación, las implicaciones de hacerlo son considerables y hay que probarlo muy bien.

Entender cómo funcionan los registros fantasma y el proceso de limpieza nos permite diagnosticar mejor posibles problemas de rendimiento relacionados con una alta actividad de eliminación y tomar decisiones más informadas sobre la gestión de nuestras bases de datos. Como administradores de SQL Server, debemos ser conscientes de este proceso silencioso pero crucial, asegurando que nuestras bases de datos no se conviertan en un cementerio de «fantasmas» olvidados.

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, Rendimiento, SQL Server, 0 comentarios