Incidencias

¿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

Especial Halloween. El marrón que todo DBA ha sufrido

¿Qué Halloween da miedo? Será para los que no han tenido que restaurar un backup en producción y estaba corrupto, con el jefe respirándoles en la nuca y un SLA expirando más rápido que el café enfriándose.

Los verdaderos horrores no salen en películas. Se ejecutan en nuestros servidores.

Si el año pasado nos pusimos nostálgicos para hablar del problema de Halloween original, hoy vamos a hablar de jobs fallidos, bloqueos eternos, desarrolladores con acceso a sysadmin, y scripts “probados en otro entorno”. Marrones que un DBA conoce muy bien. Marrones que no se borran con un ROLLBACK.

Definición técnica

MARRÓN (noun)

Término operativo usado en entornos técnicos para describir cualquier tarea, incidente o requerimiento que:

  1. No estaba prevista.
  2. Nadie quiere hacer.
  3. Tiene alta visibilidad y cero documentación.
  4. Requiere acción inmediata (generalmente ayer).
  5. Y suele implicar que el culpable eres tú, aunque no tengas ni idea de qué ha pasado.

Roles asociados:

  • Enmarronador (BrownDispatcher): Individuo que asigna el marrón con una sonrisa, un café en la mano o un “esto es rápido”. Suele tener cargo, pero no responsabilidad.
  • Enmarronado (Browned): Víctima del proceso de asignación. También conocido como “el de SQL” o “el que sabe”. No necesariamente tiene culpa, pero sí marrón.
  • Buscamarrones (BrownFinder): Persona que se ofrece voluntaria “para aprender” o “echar una mano”. A menudo desaparece misteriosamente cuando el marrón explota.
  • Comemarrones (BrownEater): DBA veterano que se ha comido tantos marrones que ya solo le quedan dos emociones: resignación y café.

Importante:

El marrón flotante: la petición que no llega (pero llegará)

Es lunes y alguien menciona “tenemos que mirar lo del rendimiento del ERP” mientras tú estás con otras 40 cosas. Nadie lo documenta, nadie lo asigna, pero ya sabes lo que va a pasar. Lo oyes flotar.

Un día (viernes a última hora, seguro) alguien abre un ticket con prioridad alta y asunto ambiguo: “Problema con SQL”. Ni reproducible, ni claro. Y para cuando llega a ti, ya eres el responsable. Del ERP. Del rendimiento. De todo.

Este marrón huele a cursores usados alegremente por algún genio que ha leído un blog equivocado. Y te va a tocar explicarlo. Otra vez.

El marrón fulminante: el «¿qué ha pasado?» a las 8:00

Son las 7:58. Te conectas. Parece que todo está tranquilo. Hasta que alguien grita por Teams: “¡la base de datos está caída!”

Miras los logs. Alguien reinició el servicio de SQL Server durante la noche. ¿Motivo? “Estaba lento y pensé que eso lo arreglaría, luego el servicio no se paraba y reinicié el servidor”. 

No sabemos qué ha pasado, solo tenemos aplicaciones paradas y usuarios locos por los pasillos.

Y tú, desayunando con una base de datos de 25 Tb en recuperación.

Este es el marrón que se instala sin pedir permiso. Lo único que puedes hacer es ponerte los guantes de forense, y rezar para que pase cuanto antes.

El automarrón: lo hiciste tú, y lo sabes

Hay veces que el marrón no viene de fuera. Te lo cocinas tú solito.

Aceptaste esa migración “sencilla” de SQL Server 2012 a 2022. Te dijeron que solo había que mover “un par de bases”. Ahora estás descubriendo jobs con código T-SQL del pleistoceno, linked servers a dominios que ya no existen, y stored procedures que funcionan por magia negra.

Y todo tiene que estar listo este fin de semana. Por supuesto.

Es el automarrón. Y el problema no es el marrón. Es tu optimismo que te llevó a no mirar dos veces.

El marrón pata negra: ese proyecto que recordarás siempre

Hay marrones, y luego está ese proyecto que te cambió la vida. El que te hizo cuestionarte tus decisiones vitales.

Un data warehouse mal diseñado que hay que “reoptimizar”. Con cientos de tablas de millones de registros sin índices, y vistas anidadas que ya tienen su propio ecosistema. Con consultas que tardan horas y desarrolladores que te juran que “antes iba rápido”.

Es el marrón pata negra. No se arregla. Se sobrevive. Y si logras salir de ahí, probablemente acabarás con un tic nervioso… y un máster en tuning sin quererlo.

El marrón de última hora: el clásico de viernes tarde

Viernes, 14:59. Recoges. Cierra sesión. Y entonces… “oye, una cosilla antes de que te vayas”.

Spoiler: no es una cosilla.

Es un cambio en producción. Un script que nadie ha revisado. Un MERGE que te juran que va bien “porque lo hemos probado en dev”. Unos índices que hay que “crear rapidito” porque el CTO ha leído en LinkedIn que eso mejora el rendimiento.

Y tú, con el abrigo puesto, ejecutando con miedo un script que viene de un Excel. Bienvenido al marrón invocado. No saldrás antes de las 21:00. Si sales.

El marrón mutante: el que cambia cada día

Te asignan un problema de rendimiento. Miras. Optimización sencilla. Pones un índice. Va mejor.

Pero luego cambia el plan de ejecución. Luego te enteras de que han cambiado el MAXDOP sin avisarte. Luego la tabla crece un 300% de un día para otro. Y el problema vuelve. Peor.

El marrón mutante nunca termina. Hoy es CPU, mañana IO, pasado bloqueos. Intentas aplicar parches, pero no hay final. Es como dar soporte a una base que se autodestruye a diario y se recompone… peor.

Vives en un entorno hostil.

El marrón sonda: el disfrazado de consulta inocente

“Una duda rápida sobre un procedimiento que tarda un poco…”

Error. Ya estás dentro.

Empieza como una duda. Luego te piden revisar el plan. Luego optimizar. Luego reescribir. Luego hacer un informe. Luego… estás haciendo tú todo.

Los marrones sonda son los peores porque son invisibles. Te atrapan por empatía. No porque puedas decir que no, sino porque ya estás en medio. Y claro, ya que estás…

El pressing brown: presión en estéreo

Cliente cabreado. Jefe nervioso. Equipo de desarrollo esperando. Todos preguntan. Todos presionan.

Y tú, el DBA, recibiendo por todos lados. Con logs, sin contexto, y con el “esto no puede estar fallando, si no hemos tocado nada” como único diagnóstico.

Es el pressing brown. Nadie lo admite, pero todos lo han hecho: tirar el marrón al de bases porque “seguro que es algo del SQL”.

Y a ti te toca arreglarlo. Sin información. Sin culpa. Sin gloria.

El brown shower: todo cae a la vez

Backup corrupto. Alerta de disco. AG desincronizada. Login bloqueado. Limpieza de datos que ha borrado datos de más (a quien se le ocurre poner las FK con borrado en cascada…). El entorno ha dicho basta. Y todo a la vez.

Eso no es un marrón. Es un brown shower.

Cuando pasa, no arreglas. Contienes. Priorizas, sobrevives, y rezas porque el clúster no decida reiniciarse. Y si alguien pregunta cómo va todo, solo di: “controlado”, mientras lloras por dentro.

Conclusión: el terror real se ejecuta en SQL

Que sí, que Halloween da miedo. Pero no tanto como ejecutar un DELETE sin WHERE en la base de producción, o que el backup del FULL no incluya las bases más críticas.

Nosotros no necesitamos sustos. Ya los trae el trabajo.

Y recuerda: si un jefe te dice “es una tarea rápida”, asegúrate de tener a mano tu última copia del CV actualizado. Y una linterna. Porque se viene noche larga.

Feliz Halloween, compañeros de marrones. Que los browners pasen de largo… o al menos os pillen con un buen plan de contingencia.

Publicado por Roberto Carrancio en Otros, 0 comentarios

Cuando un LEFT JOIN se convierte en INNER. SQL Server Basics

Lo has visto. Lo has sufrido. Y si aún no te ha pasado, enhorabuena, pero no te relajes, tu día llegará. Me refiero al clásico caso del LEFT JOIN que acaba comportándose como un INNER JOIN porque alguien decidió, en un momento de inspiración, colocar un filtro donde no debía. Y claro, la consulta no devuelve lo que se espera, o peor: devuelve justo lo que se espera, pero está mal.

Hoy vamos a hablar de un problema que se cuela en muchos desarrollos y auditorías de código: el efecto de los filtros en un LEFT JOIN, y cómo una mala ubicación puede cambiar radicalmente el comportamiento de tu consulta. Así que prepara café, que vienen curvas.

El LEFT JOIN que no era

Un LEFT JOIN debería, en teoría, devolver todos los registros de la tabla izquierda, aunque no haya coincidencia en la tabla derecha. Hasta aquí, todo bien. Pero esto solo se cumple si no saboteas la lógica desde el WHERE.

Veamos un ejemplo simple pero revelador:

A simple vista parece correcto. Queremos todos los clientes y, si tienen facturas a partir de 2025, las mostramos. Pero no. Lo que acaba devolviendo esta consulta es únicamente aquellos clientes que tienen facturas desde 2025. ¿Y los que no tienen ninguna factura? Fuera del resultado. Despedidos.

¿Por qué? Porque estás aplicando un filtro sobre una columna de la tabla derecha (Facturas) después del JOIN, pero en el WHERE, que se evalúa después del JOIN, cuando los NULL ya están presentes. Y claro, NULL >= ‘2025-01-01’ es FALSE. El motor hace su trabajo, y tú te quedas sin clientes sin facturas.

¿Dónde va el filtro entonces?

En el ON. Sí, ese que muchos usan como si fuera una casilla para emparejar claves primarias con foráneas y poco más. Pero el ON tiene un papel más interesante aquí, si metes el filtro ahí, estarás condicionando el JOIN, no el resultado final.

Así que la versión correcta sería:

Ahora sí, todos los clientes aparecen. Los que tienen facturas desde 2025 las verán en su fila. Los que no, tendrán NULL en FacturaID. Tal y como un LEFT JOIN debe comportarse.

¿Y si necesito más filtros?

Buena pregunta. No es raro que quieras filtrar también por condiciones de la tabla izquierda. ¿Qué pasa entonces? Pues ahí sí puedes usar el WHERE, con criterio.

Por ejemplo:

Esto funciona como debe. El filtro en Clientes actúa después del JOIN, pero como no anula la lógica del LEFT, todo sigue su curso. Los clientes activos salen todos, tengan o no facturas desde 2025. Así sí.

¿Y si necesito filtrar por una columna de la derecha, pero sólo cuando no sea NULL?

Aquí empieza el juego sucio. Lo que quieres, probablemente, es quedarte con clientes que tengan facturas desde 2025, pero también mostrar los que no tienen ninguna factura. En ese caso, tienes que pensar en lógica condicional.

Una forma válida sería:

Esto ya es más decente. Estás diciendo: «dame los clientes que tienen facturas desde 2025, o los que no tienen ninguna factura». Y eso se respeta.

Pero seamos sinceros, si tienes que hacer esto a menudo, algo en tu modelo de consultas no huele bien. O tu reporting, o tu forma de pensar los datos. En esos casos, es mejor plantearte si necesitas un LEFT JOIN, unas vacaciones o modificar la lógica entera de la consulta.

No es un detalle menor

Aquí no estamos hablando de una microoptimización ni de una virguería SQL. Estamos hablando de lógica rota. El tipo de error que pasa todos los entornos, llega a producción, y luego nadie entiende por qué los informes no cuadran con la realidad.

Y lo peor, es un error silencioso. No da fallo. No lanza excepciones. Simplemente te da datos incorrectos con cara de datos correctos. Lo cual, en nuestro mundo, es el infierno.

En auditorías de rendimiento y revisiones de lógica de negocio, me encuentro este patrón con una frecuencia desesperante. Muchas veces me dicen: «Pero es que es un LEFT JOIN, ¿por qué no salen todos?». Y la respuesta es casi siempre la misma: porque tú mismo has convertido ese LEFT en un INNER, sin darte cuenta.

¿Qué pasa con los ORMs?

Sí, los ORMs. Esos grandes generadores de consultas elegantes como un campo minado. Hay ORMs que optimizan mal los JOIN, o que aplican los filtros fuera del ON por defecto. Y luego te toca a ti arreglar el entuerto porque el modelo “no da para más”.

Así que si estás usando Entity Framework, Hibernate o cualquier ORM moderno, revísalo tú a mano. No confíes en que el generador de consultas haya entendido tus intenciones como tú las entiendes.

Y ya que estamos, un apunte sobre MERGE

Sí, ya sé que esto va de LEFT JOIN, pero la confusión entre el WHERE y el ON en MERGE también merece un recordatorio. Porque ahí también es fácil liarla. En un MERGE, el ON es la condición de emparejamiento, no de filtrado. Si lo usas mal, puedes terminar haciendo UPDATE a registros que no deberías tocar, o insertando cosas que ni sabías que existían.

Lo dejo ahí, pero si te interesa, en el blog ya hablé en profundidad sobre los problemas de MERGE.

Conclusión

Si vas a usar un LEFT JOIN, asegúrate de entender cómo funciona y dónde colocar los filtros. No es un tema de estilo, es un tema de lógica. Un WHERE mal puesto no solo degrada el rendimiento: rompe la semántica de tu consulta.

Así que la próxima vez que revises una query con LEFT JOIN, pregúntate si realmente está haciendo lo que debería. Y si no, ya sabes dónde mirar primero: el WHERE. Porque sí, puedes hacer un LEFT JOIN que se comporte como un INNER, y también puedes correr con los cordones desatados. Ambas cosas acaban mal.

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, 3 comentarios

Canalizaciones por nombre (Named Pipes) en SQL Server

Durante años, las canalizaciones por nombre (Named Pipes) han sido uno de esos protocolos de red que viven en las sombras de TCP/IP. Están ahí, aparecen en las configuraciones del SQL Server Configuration Manager, y de vez en cuando algún alma valiente pregunta si debería activarlas “por si acaso”. Y no, por si acaso no es un argumento válido en administración de sistemas, y mucho menos en bases de datos. Vamos a analizar qué son exactamente, cómo funcionan y en qué escenarios podrían tener sentido… si es que alguno queda en 2025.

¿Qué demonios son las canalizaciones por nombre?

Antes de decidir si las activamos o las dejamos durmiendo el sueño de los justos, conviene entender qué son. Las canalizaciones con nombre son un mecanismo de comunicación interprocesos (IPC) heredado del glorioso mundo de Windows NT. Permiten que dos procesos, incluso en máquinas diferentes, se comuniquen a través de una ruta virtual. En SQL Server, representan uno de los protocolos disponibles para aceptar conexiones de cliente.

Mientras que TCP/IP utiliza direcciones IP y puertos, Named Pipes utiliza una sintaxis como \\.\pipe\sql\query, y en red sería algo tipo \\Servidor\pipe\sql\query. Esta vía de comunicación puede ser más rápida en entornos muy controlados y locales (léase: conexión directa entre un cliente y un servidor dentro de la misma LAN y sin cuellos de botella), pero en la práctica actual… hay que hilar muy fino para justificar su uso.

TCP/IP vs Canalizaciones por nombre: ¿por qué seguimos hablando de esto?

La realidad es que TCP/IP es el protocolo estándar y recomendado para conexiones SQL Server, tanto en producción como en desarrollo. Es más robusto, más flexible, y más preparado para escenarios reales con múltiples capas de red, firewalls, balanceadores, NATs y demás fauna moderna.

Entonces, ¿por qué SQL Server sigue ofreciendo Named Pipes como opción? Por compatibilidad. Porque aún hay sistemas legacy que las usan. Y porque a Microsoft le cuesta soltar lastre tanto como a cualquiera que aún mantiene SQL Server 2008 en alguna esquina de su datacenter “temporal”.

Hay entornos donde, por motivos históricos o arquitecturas muy específicas, se configuraron conexiones a SQL Server utilizando Named Pipes. En esos casos, sí, deshabilitarlas podría romper algo. Pero esa es la excepción, no la norma.

¿Cuándo tienen sentido las Named Pipes?

Vale, no todo es blanco o negro. Hay escenarios, pocos pero existentes, donde las canalizaciones por nombre pueden tener cierta ventaja:

  • Conexiones locales (cliente y servidor en la misma máquina): En algunos benchmarks internos de Microsoft (de hace más de una década nada menos), se observó que el rendimiento de Named Pipes en conexiones locales era ligeramente superior al de TCP/IP. Pero, francamente, si ese es tu cuello de botella, tienes problemas mayores.
  • Autenticación integrada en entornos Windows puros: Las canalizaciones por nombre pueden simplificar ciertos escenarios de autenticación integrada en entornos totalmente controlados por Active Directory. Pero otra vez: TCP/IP también lo hace sin problemas.
  • Entornos legacy que no quieres (o puedes) tocar: Si tienes una aplicación que explícitamente se conecta usando np: o configuraciones hardcoded de canalizaciones con nombre, y no puedes modificarla… entonces no queda otra que habilitarlas.
  • Solución de problemas puntuales: En algunos casos, cuando el acceso por TCP/IP falla misteriosamente (DNS, puertos bloqueados, fuegos en el CPD…), usar Named Pipes puede servir para diagnosticar si SQL Server sigue vivo y coleando.

Pero si tu escenario no cae en alguno de estos puntos, las Named Pipes sobran.

Cómo funcionan realmente las canalizaciones por nombre

Cuando habilitas Named Pipes en SQL Server, el motor escucha en una canalización concreta: \\.\pipe\sql\query por defecto. El cliente debe conectarse utilizando ese nombre. Lo que muchos no saben es que esto no solo requiere que el cliente conozca la sintaxis, sino también que la resolución de nombres esté bien configurada (en red), y que no haya firewalls bloqueando el tráfico correspondiente.

Además, en entornos remotos, el protocolo puede comportarse de forma bastante torpe: mayor latencia en la negociación, más complejidad en el tráfico de red, y más exposición a errores difíciles de diagnosticar. Si te suena a dolor de cabeza… es porque lo es.

¿Qué pasa si dejo las canalizaciones por nombre activadas “por si acaso”?

Esto es como dejar todas las ventanas de casa abiertas por si te olvidas las llaves. En teoría podría ayudarte, pero en la práctica te estás exponiendo innecesariamente. Habilitar las canalizaciones por nombre sin necesitarlas abre un vector de ataque innecesario (sí, también hablamos de superficie de ataque), complica el troubleshooting de conexiones, y puede provocar que clientes mal configurados intenten conectar usando este protocolo en lugar de TCP/IP.

Por si fuera poco, cuando están activadas, SQL Server puede priorizarlas en la cadena de protocolos, lo que lleva a situaciones surrealistas como que un cliente tarde más de la cuenta en conectarse porque está intentando usar Named Pipes antes que TCP/IP.

Lo peor de todo es que, en redes modernas, usar Named Pipes puede ralentizar la conexión en lugar de mejorarla. Así que eso de activarlas para “ganar velocidad” es un mito que ya deberíamos haber dejado atrás con el disquete.

¿Y si las necesito, cómo las activo (o desactivo)?

Si a pesar de todo necesitas activar Named Pipes, el proceso es sencillo, pero no inmediato. Desde SQL Server Configuration Manager, accede al protocolo correspondiente bajo SQL Server Network Configuration, y ahí puedes habilitarlas o deshabilitarlas. Necesitarás reiniciar el servicio SQL Server para que los cambios tengan efecto.

Además, si vas a usarlas, asegúrate de configurar también correctamente la cadena de conexión en el cliente, usando el prefijo np: para forzar que se utilicen Named Pipes.

Y, por supuesto, monitoriza. No asumas que todo va mejor solo porque te conectaste. Comprueba latencias, errores, y la experiencia real del usuario. Porque los milagros no vienen de las canalizaciones.

¿Qué protocolo prioriza SQL Server?

El orden de los protocolos es importante. SQL Server Native Client (o el driver OLE DB / ODBC que uses) sigue un orden al intentar conectar, salvo que lo fuerces. Ese orden se define también en SQL Server Configuration Manager, y puedes modificarlo.

Si dejas Named Pipes activado y en primer lugar, el cliente intentará primero por ahí antes de probar TCP/IP. Si Named Pipes no está disponible o hay problemas de red, el tiempo de espera puede incrementarse de forma absurda. ¿Te suenan esas conexiones que tardan 20 segundos solo en conectar? Pues eso.

Conclusión

Las canalizaciones por nombre son como el fax, aún existen, aún funcionan, y hay quien defiende que tienen utilidad. Pero en la mayoría de escenarios modernos con SQL Server, no hay ninguna razón técnica sólida para tenerlas activadas si no se están usando.

No aportan ventaja real frente a TCP/IP, complican el diagnóstico, abren superficie de ataque, y pueden ralentizar la conexión. Si tu entorno las requiere, adelante, pero hazlo con conocimiento de causa. Si no sabes para qué las necesitas, es que no las necesitas.

Apaga las Named Pipes, reinicia tu SQL Server y duerme tranquilo sabiendo que has reducido complejidad innecesaria.

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

El libro de SQL Server que estabas esperando

Después de más de una década trabajando con SQL Server y ahora compartiendo contenido en SoyDBA, estoy escribiendo un libro pensado para profesionales que quieren ir más allá de la documentación oficial. Un libro técnico y muy didáctico.

Más de 40 capítulos en 7 partes, desde los fundamentos del modelo relacional hasta las herramientas de diagnóstico más avanzadas. Un recorrido completo, pensado para DBAs, analistas y desarrolladores que quieren entender cómo funciona SQL Server de verdad.

Portada Libro

El prólogo lo firma Fernando G. Guerrero, pionero y referente en nuestra comunidad. La contraportada, Juanjo Luna, MVP de Access y más apasionado de SQL de lo que quiere reconocer. Os dejo aquí el texto de la contraportada:

Si quieres conseguirlo corre a Amazón.

El libro está disponible en Amazon en formato papel y Kindle en todo el mundo.

Mantente al día de las novedades con mi newsletter gratuita

Logo SoyDBA

Únete a la newsletter de SoyDBA

Regístrate gratis para no perderte ninguna novedad. Te avisaré de noticias y eventos importantes

¡No hacemos spam! Lee nuestra política de privacidad para obtener más información.

Publicado por Roberto Carrancio en Alta Disponibilidad, Cloud, Índices, Rendimiento, SQL Server, 0 comentarios

MERGE en SQL Server: La eterna promesa llena de bugs

Durante años, MERGE ha sido esa promesa elegante que parecía resolver de un plumazo todo lo que nos complica la vida a los que escribimos consultas T-SQL: una única instrucción que unifica INSERT, UPDATE y DELETE. Menos código, más claridad, mejor rendimiento. El sueño húmedo de cualquier desarrollador… hasta que lo llevas a producción y te explota en la cara.

Sí, yo también quise creer. Pero tras ver más de un sistema roto por culpa de un MERGE traicionero, aprendí la lección. Y como este blog va de compartir experiencia real, no fantasías, vamos a desmontar con datos, técnica y un poco de sarcasmo, por qué MERGE es, en muchos entornos, un unicornio lleno de bugs.

El espejismo de la elegancia

La idea es sencilla y tentadora: haces una sola declaración, defines condiciones de coincidencia entre origen y destino, y luego eliges qué hacer si hay match o no. Todo en un solo bloque. Nada de IF EXISTS, nada de UPDATE por un lado e INSERT por otro. Un único statement, limpio y compacto.

El problema es que SQL Server no es un laboratorio teórico. Es un entorno donde las condiciones cambian, los bloqueos existen, los triggers hacen su trabajo (o no), y las consultas compiten por recursos. Y ahí, la belleza del MERGE se convierte en un infierno difícil de depurar.

El historial de errores no es opcional

Vamos con lo feo. Microsoft lleva más de una década acumulando errores abiertos relacionados con MERGE. Aquí no estamos hablando de edge cases oscuros, sino de fallos que afectan a la integridad de los datos.

Aaron Bertrand ya lo dejó claro hace más de siete años en su artículo «Use Caution with SQL Server’s MERGE Statement«, el operador MERGE acumulaba más de 20 errores críticos documentados en versiones de SQL Server que abarcan más de una década. Algunos tan graves como silenciosos, otros directamente absurdos. Y lo peor no es que existan, sino que muchos siguen sin resolverse a día de hoy después de años. Y cuando una funcionalidad lleva tanto tiempo con bugs abiertos y nadie los cierra, el mensaje es claro: no está pensada para producción. Punto.

Logo SoyDBA

Únete a la newsletter de SoyDBA

Regístrate gratis para no perderte ninguna novedad. Te avisaré de noticias y eventos importantes

¡No hacemos spam! Lee nuestra política de privacidad para obtener más información.

Estamos hablando de que MERGE no ejecuta triggers cuando debería, que dispara errores crípticos si hay más de una coincidencia por fila destino, que genera planes de ejecución incoherentes aunque las estadísticas estén actualizadas, o que, simplemente, no hace lo que le has dicho que haga.

Y cuando algo así puede romper datos sin avisar, no estamos ante una cuestión de estilo. Es un riesgo técnico que hay que conocer y evitar como parte del trabajo profesional.

Yo también lo he usado. Pero sabiendo dónde

Ahora bien, no todo es fuego y azufre. Yo mismo he usado MERGE con muy buenos resultados… pero en su contexto natural: procesos ETL para la carga de un Data Warehouse, sin concurrencia, sin triggers, sin usuarios esperando respuesta en tiempo real.

Ahí, MERGE brilla. En rendimiento, no tiene rival. Cuando puedes controlar el entorno y sabes que nadie va a meter la mano mientras el proceso está en marcha, la ganancia es real. Lo he usado para cargas masivas donde la lógica condicional era extensa y mantener la sincronización entre staging y destino requería algo más que un simple UPDATE. Y funcionó. Rápido, claro y sin sorpresas.

Y aun así, no es cómodo de usar

Ahora bien, incluso cuando MERGE funciona bien, escribirlo es otra historia. La sintaxis es tan compleja y poco intuitiva que soy incapaz de redactar una sentencia completa sin tener que abrir la documentación. Y no soy el único. Todos los que conozco, incluyendo perfiles senior, necesitan repasar los detalles o buscar ejemplos antes de atreverse a escribir uno no trivial. Obviamente al que escriba MERGEs a diario esto no le va pasar pero, al común de los mortales, nos cuesta y mucho.

Y no hablo de un MERGE básico, ese entra bien. Pero en cuanto mezclas múltiples condiciones, WHEN MATCHED, WHEN NOT MATCHED BY SOURCE, columnas calculadas, filtros condicionales y OUTPUT, el código se convierte en un monstruo ilegible. 

¿Es un problema grave? No, para eso está la documentación. Pero sí es un obstáculo innecesario. Más aún en un lenguaje como T-SQL, donde solemos priorizar claridad sobre compactación. Y aunque hoy en día las herramientas de IA nos pueden ayudar a escribirlo, mantenerlo sigue siendo responsabilidad nuestra. Y mantener un MERGE denso, con lógica compleja y comportamiento ambiguo, es como desactivar una bomba con guantes de boxeo.

Cuando MERGE falla… ni siquiera sabes por qué

Uno de los problemas más serios de MERGE es que sus errores no siempre son explícitos. En algunas situaciones simplemente no hace nada, o peor: hace algo, pero no lo que esperabas. Y ni el plan de ejecución, ni el @@ROWCOUNT, ni el log de errores te dan pistas.

Por ejemplo, si en el ON de un MERGE hay ambigüedad o más de una fila coincidente por destino, puedes recibir un error como:

The MERGE statement attempted to UPDATE or DELETE the same row more than once.

¿Y sabes qué fila fue? No. ¿Sabes por qué coincidieron dos filas? Tampoco, a menos que te pongas a hacer debugging con CTEs, filtros y OUTPUT hasta encontrarlo. Una joya para los que trabajamos bajo presión.

Triggers, locking y otras trampas técnicas de MERGE

Otra razón para evitar MERGE es su relación tóxica con los triggers. Por ejemplo, si tienes un INSTEAD OF TRIGGER, debe cubrir todas las acciones (INSERT, UPDATE, DELETE) usadas en el MERGE. Si no, obtendrás un error.

Además, dentro de los triggers, @@ROWCOUNT refleja la suma de todas las operaciones ejecutadas por el MERGE, no por separado. Así que no sabes si te han hecho un INSERT, un DELETE o un UPDATE. Y como $action no es accesible en el contexto del trigger… buena suerte.

A esto súmale que, en entornos de concurrencia, MERGE no garantiza aislamiento ni orden de ejecución. Necesitas usar explícitamente HOLDLOCK en la tabla destino para evitar condiciones de carrera, lo cual puede afectar gravemente al rendimiento o incluso provocar deadlocks si no lo haces bien.

Si Microsoft no usa MERGE… tú tampoco deberías

Hay una máxima que sigo como DBA, si ni el propio motor de SQL Server usa una funcionalidad para sus operaciones internas, será por algo. ¿O tú irías a comer a un restaurante donde ni el propio chef se come la comida que cocina?  Y adivina qué: Microsoft no utiliza MERGE internamente en sus procesos de sistema. Ningún SP del sistema usa MERGE. Ninguno.

Tampoco verás MERGE en herramientas como Replication, Change Tracking, CDC o Sync Framework. ¿Casualidad? No. Evitan su uso por inestabilidad, falta de garantías y dificultad de mantenimiento.

¿Y entonces qué usamos? Alternativas a MERGE

Lo que propongo no es TRY HARD SQL. Es simple, robusto y ha demostrado funcionar durante décadas:

Sí, son más líneas. No, no es sexy. Pero sabes lo que hace, puedes depurarlo, y es seguro en entornos OLTP. También puedes encapsularlo en un SP, usar OUTPUT para auditar, y meterlo en transacciones controladas. Resultado: código mantenible, auditable y libre de sorpresas.

Otra opción válida en operaciones masivas o ETL es usar UPDATE por separado, seguido de INSERT con NOT EXISTS. Ejemplo clásico:

Más claro, más predecible y con control absoluto de cada paso.

¿Y qué pasa con el rendimiento?

No podemos cerrar el tema sin hablar de la razón por la que muchos defienden MERGE con uñas y dientes: el rendimiento. La posibilidad de hacer INSERT, UPDATE y DELETE en una única operación suena, y es, más eficiente que dividir el trabajo en tres pasos.

Y sí, cuando funciona bien, MERGE puede reducir el número total de lecturas. El optimizador puede acceder a las tablas implicadas una sola vez, generar un plan compacto, y ejecutar todo con menos I/O que si lanzas varias operaciones por separado. Eso, en entornos de grandes volúmenes, marca la diferencia.

Pero, y este «pero» es importante, todo ese rendimiento se evapora en cuanto las condiciones dejan de ser ideales. Si tienes triggers, estadísticas obsoletas, datos duplicados, falta de unicidad, o cualquier mínima sorpresa en los datos, el plan de ejecución puede degradarse. Y si encima falla en mitad del proceso, el coste de arreglarlo supera con creces cualquier ganancia de rendimiento.

La pregunta no es solo “¿es más rápido?”, sino “¿cuánto me cuesta cuando va mal?”. Porque, al menos para mí, un MERGE que tarda 2 segundos pero rompe una fila cada 10.000 es mucho peor que un UPDATE + INSERT de 15 segundos que funciona siempre.

Dicho eso, si lo usas en un proceso ETL bien contenido, con staging controlado, sin usuarios ni competencia por recursos, y puedes probar cada camino lógico… adelante. En esos casos, sí, el rendimiento justifica su uso y, como os decía antes, yo mismo lo he usado.

¿Se puede usar MERGE con garantías?

La respuesta corta es sí, pero sólo bajo ciertas condiciones. Y con cuidado quirúrgico.

  • No debe haber concurrencia. Ni un solo proceso paralelo tocando las mismas filas.
  • Debe usarse HOLDLOCK explícito.
  • Las condiciones del ON deben garantizar unicidad absoluta.
  • Debes probarlo exhaustivamente, incluyendo casos límite y datos duplicados.
  • Y por favor, nada de usarlo con triggers activos. Ahí directamente estás pidiendo un exorcismo.

¿Vale la pena todo ese esfuerzo para no escribir cinco líneas más? En procesos ETL donde los datos son temporales, y el entorno está bien controlado, puede tener sentido. En entornos críticos OLTP… ni loco.

Conclusión

MERGE es eficiente. Compacto. Potente. Y en los entornos correctos, puede ser una joya. Pero en SQL Server, también es frágil, lleno de trampas y muy poco transparente cuando falla. No es cuestión de gustos, es cuestión de riesgos.

Si lo vas a usar, hazlo con pleno conocimiento de sus limitaciones y con un entorno controlado. Y si no puedes controlar todo lo que MERGE necesita para no traicionarte… mejor escribe unas líneas más y duerme tranquilo.

Porque a veces, la mejor optimización es no tener que hacer un análisis de errores.

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

¿Cuál es el problema que quieres resolver?

Si hay una pregunta que debería resonar en la cabeza de todo DBA antes de tocar nada es esta: ¿cuál es el problema que quieres resolver?. Parece de sentido común, pero basta con ver la cantidad de “optimizaciones” fallidas que nos encontramos a diario para entender que muchos prefieren actuar primero y pensar después.

No es cuestión de señalar a los juniors recién llegados. También he visto seniors con experiencia aplicar consejos a ciegas porque lo leyeron en un blog (sí, incluso en este). Optimizar sin tener claro el objetivo es la mejor forma de acabar con más índices de los que puedes contar y un servidor que corre peor que antes.

Cuando dos índices son un problema

Hace un tiempo publiqué un artículo en el blog que mostraba un comportamiento extraño en SQL Server: si creas dos índices con los mismos campos pero en orden inverso, el optimizador puede entrar en bucle y empeorar el rendimiento.

No es que uno de los índices fuera inútil, es que la mera coexistencia de ambos confundía al motor. El resultado eran planes de ejecución incoherentes, lecturas disparadas y un servidor que parecía castigar la osadía de haber creado ese segundo índice.

Lo comprobé en mi propio laboratorio y sí, el problema existe en distintas versiones, desde 2014 hasta 2022. No hay explicación oficial, pero el efecto es evidente: un índice que no se usa puede penalizar tanto como uno mal diseñado.

La moraleja es clara: no todo lo que parece buena idea lo es. Crear un índice extra “por probar” puede desatar un problema que no estaba ahí antes.

El optimizador también se equivoca

A ese escenario extraño se suma otro que me he encontrado demasiadas veces: el optimizador sugiriendo crear un índice que ya existe.

¿Cómo puede ser? Muy simple: lo que realmente falla son las estadísticas. Si los datos de distribución están obsoletos, el optimizador cree que no tiene un índice adecuado y recomienda uno nuevo. Y como “buenos DBAs obedientes”, lo creamos. Resultado: en el mejor de los casos acabamos con dos índices redundantes, el motor igual de confundido y el verdadero problema (las estadísticas) intacto. En el peor, con el problema que hemos descrito en el apartado anterior y con las estadísticas aún desactualizadas.

La historia se repite: en vez de preguntarnos qué queremos resolver, nos dejamos llevar por la sugerencia de turno y complicamos aún más el entorno.

Optimizar sin objetivo: el problema más común

Ambos casos ponen de manifiesto lo mismo: aplicar soluciones sin entender el problema real es un deporte de riesgo.

Ya lo comentamos en este otro artículo, los entornos de bases de datos no se arreglan con recetas universales ni con “scripts definitivos” de Internet. Cada entorno es distinto, con sus cargas, sus decisiones históricas y sus miserias. Copiar y pegar soluciones genéricas es más postureo técnico que administración seria.

La optimización sin objetivo es como disparar con los ojos cerrados, con suerte no das en nada, pero la mayor parte del tiempo acabas dañando algo.

La pregunta de Brent Ozar

Aquí entra la insistencia de Brent Ozar en su mantra favorito y que da titulo a este artículo:
¿Cuál es el problema que quieres resolver?

Antes de crear índices, actualizar estadísticas o aplicar hints a lo loco o cambiar configuraciones en el servidor hay que responder a esa pregunta. ¿El problema es el tiempo de respuesta? ¿Consumo excesivo de CPU? ¿Bloqueos? ¿O solo que alguien quiere que una consulta de 3 segundos baje a 0,3 porque sí?

Uno de esos es un problema técnico. El otro es un problema de expectativas. Y mezclar ambos lleva a dedicar horas a optimizar lo que no importa mientras lo urgente sigue sin atenderse.

Si no mides, no sabes si mejoras o empeoras

Aquí está el punto que muchos olvidan, si no sabes que tienes un problema y no mides antes, no puedes saber si lo que hiciste fue una mejora o un desastre.

Un cambio sin línea base no es optimización, es puro azar. Y en producción, jugar al azar es abrir la puerta a que mañana alguien te pregunte por qué el sistema está peor y no tengas respuesta. Sin métricas antes y después, la optimización es indistinguible del postureo.

Y ojo, también tienes que medir cuando todo funciona bien, sin esa linea base es imposible saber si de verdad algo va mal o es solo la sensación de los usuarios. Ya sabemos que adoran decir: “esto ahora va más lento” sin pruebas.

Diagnóstico antes de cirugía

La única forma de trabajar en serio es diagnosticar antes de tocar. Eso implica medir, observar y recopilar evidencia. Query Store, planes de ejecución, Extended Events, DMVs… las herramientas están ahí, pero hay que usarlas con criterio.

El motor se equivoca, los consejos de Internet también, y a veces hasta nuestras “buenas prácticas” son contextuales y no universales. La diferencia entre un DBA que arregla problemas y otro que los multiplica está en hacerse siempre la pregunta incómoda antes de optimizar.

Conclusión

Cambiar configuraciones del servidor, crear índices o aplicar consejos sin diagnosticar el problema real no es optimización, es azar.

SQL Server no premia las soluciones automáticas. Premia el análisis. Y la diferencia entre un DBA que arregla cosas y otro que las rompe está en hacerse siempre esa pregunta incómoda antes de tocar nada: ¿cuál es el problema que quiero resolver?

Si no sabes que tienes un problema y no lo mides antes, no tienes forma de saber si tu cambio fue mejora o desastre. Y en bases de datos, la duda casi siempre juega en tu contra.

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