Roberto Carrancio

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

Usar siempre dbo en SQL Server: ventajas, riesgos y buenas prácticas

Después de analizar cómo funciona la propiedad db_chainig y los riesgos de activar cross db ownership chaining a nivel de instancia, nos queda un tema que está en el centro de todo: el uso del usuario y esquema dbo de forma sistemática. Es una práctica habitual, cómoda y hasta cierto punto tradicional en SQL Server, pero que conviene revisar con detalle cuando hablamos de seguridad, despliegues controlados y gobernanza de datos.

¿Realmente es buena idea que todo pertenezca a dbo? ¿Qué consecuencias tiene a medio y largo plazo? ¿Dónde está el equilibrio entre comodidad y control?

Qué es dbo y por qué se usa tanto

El usuario dbo (database owner) es un usuario especial en cada base de datos de SQL Server. Está vinculado al propietario real de la base de datos y tiene todos los privilegios dentro de ella. Cuando creamos objetos sin especificar un esquema, y el usuario con el que trabajamos está asignado como dbo, los objetos se crean bajo ese esquema por defecto.

Esto significa que en la mayoría de los entornos, a menos que se especifique lo contrario, todos los procedimientos, tablas, vistas y funciones acaban siendo del esquema dbo y perteneciendo al usuario dbo.

Esta práctica tiene una ventaja clara: evita problemas de permisos y simplifica el acceso a los objetos, sobre todo en proyectos donde no se quiere dedicar tiempo a gestionar esquemas ni propietarios. En resume, es una forma de trabajar directa y suele «funcionar a la primera». Pero también tiene implicaciones importantes que no se deben ignorar.

Seguridad: encadenamiento de propietarios dbo y permisos no deseados

Cuando todos los objetos son propiedad de dbo, es muy fácil que se establezcan cadenas de confianza sin querer. Esto es justo lo que habilita la propiedad db_chainig cuando está activa, y lo que se multiplica peligrosamente si además se activa cross db ownership chaining a nivel de instancia.

Al compartir el mismo propietario, los objetos pueden acceder entre sí sin que se comprueben los permisos del usuario que ejecuta la operación. Esto puede ser deseable en ciertos diseños bien controlados, pero también abre la puerta a que procedimientos maliciosos o mal diseñados escapen a la lógica de seguridad prevista, sobre todo cuando hay múltiples bases de datos con el mismo esquema y propietario.

Además, si todos los objetos son de dbo, resulta más difícil aplicar políticas de control por esquema o restringir accesos según áreas funcionales, ya que todo está en el mismo «cajón».

Mantenimiento y legibilidad: lo que hoy parece fácil con dbo, mañana es un dolor

Usar siempre dbo dificulta la organización lógica de los objetos. Cuando una base de datos empieza a crecer, no hay forma rápida de agrupar o identificar objetos por módulo funcional, equipo responsable o ciclo de vida.

Tampoco ayuda en tareas como generar scripts de despliegue por módulos, aplicar permisos diferenciados según esquema, monitorizar la actividad por subsistemas o delegar administración de partes concretas de la base de datos.

Todo eso requiere una separación explícita por esquemas o, al menos, una política clara de nomenclatura y propiedad. Si todo está bajo dbo, estas tareas se convierten en búsquedas manuales y excepciones constantes.

Auditoría y trazabilidad: todos los caminos llevan a dbo

Desde el punto de vista de la auditoría, tener todos los objetos bajo dbo borra cualquier pista sobre quién creó o modificó qué. El seguimiento de cambios se vuelve opaco, y las herramientas de trazado o revisión de permisos no pueden distinguir entre distintos propietarios lógicos. Esto se agrava si además usamos usuarios compartidos o impersonificación (EXECUTE AS), donde es muy difícil reconstruir el contexto original de ejecución.

Cuando una base de datos es pequeña y la usa un solo equipo, esto puede parecer un problema menor. Pero en sistemas colaborativos, auditados o con cumplimiento normativo, no tener separación de propietarios o esquemas puede ser un problema serio.

Despliegue controlado: lo que no se define, se rompe

Si trabajamos con plantillas de despliegue, versionado de bases de datos o pipelines CI/CD, usar siempre dbo limita la capacidad de definir entornos reproducibles y con permisos granulados. No se puede diferenciar entre objetos internos, públicos, temporales o propios de cada equipo.

Además, algunos errores de despliegue tienen que ver precisamente con suposiciones implícitas sobre el esquema o el propietario: si un script no especifica dbo y el usuario por defecto cambia, el objeto puede terminar en otro esquema, provocando errores sutiles y difíciles de detectar.

¿Cuándo sí tiene sentido usar dbo?

No todo es negativo. Hay escenarios donde usar exclusivamente dbo puede ser razonable. Por ejemplo en bases de datos monolíticas mantenidas por un solo equipo, en proyectos personales o experimentales donde no hay requisitos de seguridad ni mantenimiento a largo plazo o en aplicaciones donde toda la lógica de negocio está encapsulada y no se expone acceso directo a las tablas.

En estos casos, usar dbo reduce fricción y permite concentrarse en el desarrollo funcional. Pero, incluso en estos casos, conviene tener claro que estamos asumiendo una simplificación consciente, no una buena práctica general.

Buenas prácticas recomendadas: ¿dbo si o no?

Si queremos mantener un equilibrio entre simplicidad y control, podemos aplicar algunas de estas pautas.

La primera sería no usar dbo y usar esquemas distintos para separar áreas funcionales, como ventas, compras, auditoria, etc. Especialmente debemos evitar crear objetos como dbo si el usuario final no debe tener control total. En estos casos asignar esquemas a roles o usuarios concretos, y establecer políticas de permisos basadas en ellos es clave. Además, definir el esquema siempre de forma explícita en scripts y objetos programables es imprescindible para que, si un usuario tiene otro esquema por defecto, todo siga funcionando. Por último documenta siempre la política de propiedad y esquema como parte del diseño de cada base de datos.

Con estas simples recomendaciones podrás mantener la claridad y la organización incluso en proyectos con muchos objetos o múltiples desarrolladores.

Conclusión

Usar dbo para todo es cómodo, pero a menudo es una señal de diseño descuidado o sin política de seguridad clara. Aunque puede parecer una solución rápida, a largo plazo complica el mantenimiento, debilita el control de accesos y dificulta la trazabilidad.

En bases de datos pequeñas o entornos cerrados, puede no suponer un problema. Pero si trabajamos en entornos colaborativos, con ciclos de vida largos, requisitos de auditoría o despliegues automatizados, separar esquemas y propietarios deja de ser una opción y se convierte en una necesidad.

No se trata de demonizar dbo, sino de dejar de usarlo como valor por defecto para todo. Definir y respetar una estructura clara es una de las formas más efectivas de asegurar calidad y sostenibilidad en nuestras bases de datos.

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

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

Cross DB Ownership Chaining en SQL Server

En el artículo anterior vimos cómo la propiedad db_chainig permite que el encadenamiento de propietarios funcione entre objetos ubicados en bases de datos distintas, siempre que esta opción esté activada en ambas bases de datos y se cumplan ciertos requisitos. Pero hay un aspecto que no abordamos en profundidad: qué ocurre cuando esta configuración se aplica no solo a nivel de base de datos, sino también a nivel de instancia, utilizando la opción cross db ownership chaining. Este ajuste tiene un comportamiento más agresivo y, en la mayoría de los entornos, representa una mala práctica de seguridad que conviene evitar.

¿Qué hace exactamente cross db ownership chaining?

Mientras que la propiedad db_chainig se puede activar o desactivar de forma individual en cada base de datos, la opción cross db ownership chaining es una configuración de instancia que fuerza la activación del encadenamiento de propietarios entre todas las bases de datos de la instancia, sin posibilidad de excluir ninguna.

Activarla equivale, a efectos prácticos, a establecer db_chainig = ON para todas las bases de datos, aunque no lo hayamos hecho explícitamente. Esto significa que cualquier procedimiento, vista, función u otros objetos en una base de datos que comparta propietario con una tabla en otra podrá acceder a ella sin que el usuario tenga permisos explícitos. Y lo más preocupante: este comportamiento se extiende automáticamente a cualquier base de datos nueva que se cree en la instancia.

Cómo activar y desactivar cross db ownership chaining

Podemos consultar el estado actual de la opción con:

Si devuelve valor 1, la opción está activada. Para desactivarla (que es lo recomendable en la mayoría de casos):

Esta configuración requiere nivel de sysadmin, ya que afecta directamente a la seguridad global del motor.

Implicaciones de seguridad

El principal riesgo de mantener esta opción activa es que se rompen los límites de seguridad entre bases de datos. En lugar de tener compartimentos estancos, con cross db ownership chaining se permite el acceso entre objetos de distintas bases de datos con solo compartir el mismo propietario. Esto debilita el aislamiento lógico que SQL Server garantiza por defecto.

Imaginemos una instancia donde convivieran varias bases de datos de distintos departamentos, proyectos o incluso clientes. Si todas ellas utilizan el esquema y usuario dbo, y se activa cross db ownership chaining, cualquier procedimiento mal diseñado o malicioso podría acceder a tablas fuera de su alcance original sin necesidad de permisos explícitos. Este escenario rompe cualquier principio de defensa en profundidad.

Además, si se utilizan funciones como EXECUTE AS, se complica todavía más el análisis de los permisos efectivos, y pueden aparecer brechas difíciles de detectar hasta que ya es tarde.

¿Hay algún caso donde activarla tenga sentido?

La única situación razonable para activar cross db ownership chaining es en entornos completamente controlados, con una única aplicación propietaria desplegada en la instancia, donde todas las bases de datos forman parte de la misma solución y no hay usuarios externos ni terceros ejecutando código dinámico o creando objetos.

Incluso en ese caso, sigue siendo preferible activar db_chainig solo en las bases de datos que lo necesiten, en lugar de aplicar un cambio de instancia que afectará a todo lo que esté ejecutándose ahora y en el futuro.

También puede tener cierta justificación en entornos de desarrollo o pruebas rápidas, donde se valore más la agilidad que la seguridad. Pero ahí conviene recordar que muchas veces lo que se prueba en desarrollo acaba en producción… y con ello los errores de configuración.

Buenas prácticas y recomendaciones

Para mantener la seguridad y el control en nuestras instancias de SQL Server, las pautas más recomendables respecto a esta configuración son claras: “Mantener cross db ownership chaining desactivado por defecto”. Siempre es más recomendable activar db_chainig solo en las bases de datos que lo requieran y siempre de forma explícita y documentada. En cualquier caso, además te recomiendo evitar el uso masivo del propietario dbo en todas las bases de datos, especialmente en entornos con múltiples aplicaciones o proyectos y revisar regularmente qué bases de datos tienen esta propiedad activada y por qué. Tampoco está de más aplicar control de cambios y auditoría cuando se modifiquen opciones de instancia.

Además, en cualquier revisión de seguridad o auditoría formal, tener esta opción activada puede ser motivo de alerta o incluso de no conformidad con normativas como ISO 27001, PCI-DSS o el Esquema Nacional de Seguridad (ENS).

Conclusión

La opción cross db ownership chaining es uno de esos ajustes que, a simple vista, puede parecer útil para evitar errores de permisos durante el desarrollo, pero que en producción representa una puerta abierta a problemas de seguridad difíciles de rastrear. Es una opción de legado que hoy en día solo deberíamos tocar si entendemos perfectamente sus implicaciones.

Si en tu entorno necesitas acceso cruzado entre bases de datos, activa db_chainig solo donde sea estrictamente necesario. Y si lo que quieres es encapsular accesos sin exponer tablas directamente, plantéate usar certificados para firmar procedimientos y mantener un modelo de permisos más seguro y robusto.

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

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

Cómo actúa db_chainig en SQL Server

En su día publicamos un artículo donde analizábamos en profundidad los riesgos de activar la propiedad db_chainig en nuestras bases de datos: db_chainig: una configuración de seguridad peligrosa. Allí abordamos su impacto en la superficie de ataque de una instancia de SQL Server, especialmente cuando se activa de forma global o sin tener un control estricto de propietarios y permisos.

Hoy retomamos el tema con un enfoque diferente. Vamos a mostrar una demo práctica que responde a una duda concreta planteada por un integrante de la comunidad de Telegram “SQL Server Español”, y que nos permite ver de forma clara cómo se comporta esta propiedad cuando varios objetos en distintas bases de datos se relacionan entre sí. Aprovecharé además para precisar algunos matices técnicos que suelen pasar desapercibidos, sobre todo cuando intentamos aplicar el modelo de encapsulación de acceso sin otorgar permisos directos.

Recordatorio: qué es db_chainig y por qué nos debe importar

Como ya explicamos en el artículo de hace meses, db_chainig es una propiedad que habilita el encadenamiento de propietarios entre bases de datos distintas. Cuando está desactivada (valor por defecto), SQL Server impide que un objeto de una base de datos acceda a otro en una base diferente, aunque ambos compartan el mismo propietario y aunque el encadenamiento funcionase sin problema dentro de una única base de datos.

Este comportamiento se introdujo por motivos de seguridad, y su activación requiere un análisis muy cuidadoso. No obstante, existen escenarios legítimos donde permitir este acceso cruzado puede simplificar la arquitectura, como cuando separamos lógica de negocio y almacenamiento físico en distintas bases de datos bajo el mismo control.

La demo: ejemplo de acceso cruzado encapsulado entre bases de datos

Para mostrar cómo se comporta realmente esta propiedad en un escenario típico, he preparado una demo que tenéis disponible aquí.

El entorno consiste en dos bases de datos una con una vista de una tabla de la otra base de datos (podríamos llevarlo al mundo real como una base de datos donde residen los procedimientos que encapsulan la lógica de acceso) y otra base de datos con una tabla (que sería la base de datos donde se encuentran las tablas con los datos reales)

Creamos un usuario solo con privilegios explícitos sobre la base de datos con la vista e intentamos seleccionar la vista que, recordad, lee una tabla de la otra base de datos.

En un primer lugar obtenemos un error porque el usuario no existe en la otra base de datos pero si lo creamos, sin permisos, claro, nos encontramos con otro error porque el usuario no puede acceder a la tabla. 

Hay que recordar que la vista filtra los datos de la tabla por lo que en ningún caso queremos dar al usuario permisos explícitos sobre la tabla, solo debe consultar la información a través de la vista.

Con db_chainig desactivado, la ejecución falla con un error de permisos, incluso si ambos objetos tienen el mismo propietario (dbo). Al activar la propiedad en ambas bases de datos el procedimiento funciona sin que el usuario tenga permisos directos sobre la tabla. Esto demuestra cómo SQL Server evalúa las cadenas de confianza solo si dicha propiedad está activa.

Consideraciones clave sobre propietarios y contexto de ejecución

Uno de los matices más importantes que se observa en la demo es que el encadenamiento entre bases de datos solo se permite si los objetos involucrados tienen el mismo propietario y si la propiedad db_chainig está activa en ambas bases de datos.

Otro detalle importante es que el contexto de ejecución no ha cambiado mediante EXECUTE AS o similar, lo que invalidaría la cadena de confianza.

Esto significa que no basta con activar la propiedad en una sola base de datos. Además, hay que evitar que el procedimiento se ejecute en un contexto que no sea el del usuario propietario de ambos objetos. Este tipo de situaciones no siempre son evidentes y pueden complicar el diagnóstico cuando un procedimiento aparentemente correcto lanza un error de acceso denegado.

Seguridad frente a conveniencia: ¿cuándo tiene sentido usar db_chainig?

Tal y como ya señalamos en el artículo anterior, activar db_chainig a nivel de instancia es algo que deberíamos evitar en la mayoría de escenarios, sobre todo si trabajamos en un entorno multi-tenant o con bases de datos de aplicaciones distintas.

Sin embargo, si tenemos un entorno controlado, donde todas las bases de datos son desplegadas por nosotros, los objetos comparten propietario y los accesos están bien encapsulados, db_chainig puede ser una herramienta útil para simplificar la gestión de permisos y mantener el modelo de mínima exposición.

En ese contexto, lo importante es no relajarse: hay que documentar cada caso en el que se activa, asegurarse de que no hay EXECUTE AS, revisar periódicamente los propietarios de los objetos y evitar cualquier modificación que pueda romper la cadena de confianza sin darnos cuenta.

Alternativas a db_chaining: firma con certificados

Para aquellos entornos donde la seguridad es prioritaria, pero aún así necesitamos encapsular accesos entre bases de datos, lo más recomendable es firmar los procedimientos almacenados con certificados. Esta técnica, algo más compleja de implementar, permite simular una elevación de permisos perfectamente controlada y auditable, sin necesidad de alterar propiedades globales ni confiar en la coherencia del dbo.

Si todavía no has trabajado con este enfoque, te recomiendo investigar cómo funciona la creación de certificados, usuarios basados en certificado y firma de procedimientos. Puedes consultar el artículo que publiqué sobre el tema la semana pasada.

Conclusión

Con esta demo he querido complementar el análisis previo sobre db_chainig, esta vez desde un enfoque más práctico. El comportamiento por defecto protege nuestras bases de datos, pero hay casos reales donde su activación puede facilitar una arquitectura limpia y modular.

Eso sí, hay que entender muy bien sus implicaciones, probarlo a fondo y asegurarnos de que el entorno cumple con los requisitos de seguridad y propiedad para que el encadenamiento funcione sin sorpresas. Y si no queremos depender de esta propiedad, las firmas con certificados siguen siendo la alternativa más elegante y segura.

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, 2 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

Procedimientos almacenados para auditoría en SQL Server; Cuando el «sálvese quien pueda» no es estrategia

Llevamos años insistiendo en la importancia de tener auditoría en los sistemas. Y no, no hablamos de perseguir al becario por haber hecho un DELETE sin WHERE. Hablamos de una estrategia seria, sostenible y bien diseñada para dejar constancia de qué ocurre dentro de nuestras bases de datos. Y aquí es donde los procedimientos almacenados (sí, esos viejos conocidos de mil batallas) pueden jugar un papel muy digno si sabemos usarlos con cabeza.

Porque seamos honestos: muchos sistemas de auditoría empiezan con buenas intenciones y terminan convertidos en un Frankenstein lleno de triggers, jobs ocultos y tablas que nadie se atreve a truncar. Pero no tiene por qué ser así. Vamos a hablar de cómo diseñar procedimientos almacenados que nos permitan registrar operaciones relevantes, mantener la trazabilidad sin comprometer el rendimiento, y, sobre todo, que no nos obliguen a pedir perdón cada vez que ejecutamos un SELECT contra la tabla de auditoría.

¿Por qué procedimientos almacenados y no triggers o Extended Events?

Antes de lanzarnos a picar código como si no hubiera mañana, pongamos un poco de contexto. Sí, podríamos usar triggers para capturar operaciones DML. Sí, Extended Events son una maravilla para ciertas trazas. Pero aquí venimos a hablar de soluciones controladas, personalizables y que se integren de forma limpia con nuestras operaciones diarias.

Los procedimientos almacenados nos permiten centralizar la lógica de inserción, modificación y eliminación, y al mismo tiempo registrar en una tabla de auditoría todo lo que nos interesa: quién, cuándo, qué y desde dónde. Si el acceso a los datos está canalizado a través de SPs, tenemos el terreno perfecto para auditar sin traumas. ¿Que no todo pasa por SPs? Entonces el problema no es la auditoría, es el diseño general de la aplicación. Y eso es otra guerra.

Diseño de la tabla de auditoría: no empecemos la casa por el tejado

Antes de ponernos a escribir procedimientos, necesitamos tener clara nuestra tabla de auditoría. No, no vale con un INSERTED tirado en una tabla con columnas tipo Campo1, Campo2, y una nota que diga “aquí va lo que sea”. Una buena tabla de auditoría tiene estructura, contexto y propósito. Una estructura mínima (y funcional) podría ser algo así:

Y aquí hay que hacer una pausa. Porque sí, el campo Datos contiene JSON, pero no es de tipo JSON. ¿Por qué? Porque SQL Server, hasta la versión 2022, no soporta el tipo JSON nativo en entornos on-premises. En Azure SQL Database ya existe, y todo apunta a que SQL Server 2025 lo incorporará. Pero mientras tanto, usamos NVARCHAR(MAX).

Y sí, uso JSON. No, no me he pasado al NoSQL. Pero si queremos registrar los datos afectados sin necesidad de 50 columnas por cada tabla, el JSON nos da flexibilidad. 

No porque nos guste —aunque a muchos sí—, sino porque es lo que hay. Y además, no nos engañemos, el JSON nos da lo que necesitamos: almacenar estructuras flexibles y trabajar con ellas fácilmente gracias a funciones como JSON_VALUE, OPENJSON o ISJSON(). En resumen: guardamos JSON como texto, pero con cabeza.

Procedimientos almacenados con lógica de auditoría: así sí

Ahora que tenemos clara la tabla, vamos a por la lógica. El patrón es simple: cada procedimiento almacenado que realice operaciones sobre los datos deberá incluir una llamada al procedimiento de auditoría, justo después de la operación. Imaginemos un SP de actualización de empleados. Este es un ejemplo limpio y funcional, luego vosotros lo complicáis lo que queráis.

Y el procedimiento InsertarAuditoria, que sigue siendo igual de simple:

Nada de magia negra, solo lógica clara, controlada y auditable en sí misma.

Cuándo montar auditoría (y cuándo no): porque registrar cada pestañeo no es auditar

No todo merece una entrada en la auditoría. Si registramos cada acceso, cada SELECT o cada ejecución de SP, acabaremos con una tabla de 2 TB que nadie se atreve a consultar y que ralentiza cada INSERT. Hay que auditar cambios de estado relevantes: inserciones, modificaciones, eliminaciones y, dependiendo del negocio, consultas que tengan implicaciones legales o de privacidad. Auditar todo “por si acaso” es tan útil como llevar paraguas en el desierto. Y casi igual de cómodo.

Versionado de datos vs auditoría: primos lejanos

Una confusión habitual es pensar que la auditoría y el versionado son lo mismo. Spoiler: no lo son. Auditar es dejar constancia de qué ocurrió, quién lo hizo y cómo. Versionar es guardar el estado completo anterior de un registro, por si hay que volver atrás o comparar. Podemos combinar ambos enfoques, pero no deberíamos mezclarlos como si fueran equivalentes.

Para el versionado tenemos técnicas distintas como el libro de contabilidad, o incluso estructuras propias si el estándar se nos queda corto. Si lo que queremos es saber quién cambió qué, la auditoría nos basta. Si queremos saber qué valores tenía antes, entonces toca sacar la artillería del versionado.

Auditoría y rendimiento: ese delicado equilibrio

Registrar operaciones añade carga. No importa lo minimalista que sea el SP de auditoría: es una operación más por cada modificación. Si nuestra base de datos gestiona 10.000 cambios por minuto, quizás tengamos que plantear estrategias adicionales, como colas (Service Broker, por ejemplo) o particionado de la tabla de auditoría.

Y no, no vale con poner la tabla en otra base de datos para “que no moleste”. Separar el almacenamiento está bien, pero si la transacción espera a que el log de la otra base escriba… seguimos teniendo cuello de botella.

Diseñar una auditoría eficiente es como diseñar un buen índice: no basta con saber que existe, hay que entender cómo afecta a todo lo demás.

Conclusión

Los procedimientos almacenados siguen siendo una herramienta poderosa para implementar auditorías en SQL Server, especialmente cuando tenemos control sobre cómo accede la aplicación a los datos. Nos permiten registrar cambios con contexto, mantener el rendimiento bajo control y evitar la opacidad de soluciones como triggers masivos o rastreos indiscriminados.

Y sí, usamos JSON. Aunque hoy lo almacenamos como NVARCHAR(MAX), es una decisión consciente, no un apaño. Cuando SQL Server soporte el tipo JSON nativamente, y lo hará porque ya lo hace en Azure, estaremos listos para aprovecharlo sin rehacer nada.

Así que no, no necesitas un monstruo de triggers ni una solución de terceros para saber qué está pasando en tu sistema. A veces, basta con un poco de disciplina, procedimientos bien pensados… y la firme convicción de que auditar no es opcional. Es supervivencia.

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

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

Firmar procedimientos almacenados con certificados

Una política de permisos mínimos para usuarios (y roles) es fundamental para la seguridad de nuestros datos. Sin embargo, a menudo nos enfrentamos al dilema de cómo hacerlo sin sacrificar funcionalidad ni exponer en exceso nuestros objetos. Como decía, la necesidad de implementar un principio de privilegios mínimos es esencial en entornos seguros y controlados, especialmente en bases de datos con múltiples aplicaciones o equipos interactuando. Una técnica muy potente y poco utilizada en SQL Server es la firma de procedimientos almacenados con certificados. Hoy vamos a explorar cómo funciona y por qué puede marcar una diferencia importante en nuestras estrategias de seguridad.

Delegación segura con certificados

Cuando se habla de conceder permisos a través de procedimientos almacenados, una de las primeras aproximaciones suele ser el uso de EXECUTE AS. Aunque funcional, esta técnica presenta inconvenientes, especialmente cuando entra en juego el “chaining” de ejecución o cuando queremos evitar tener que gestionar contextos de ejecución elevados.

Ahí es donde entran los certificados. Firmar procedimientos almacenados nos permite encapsular permisos de forma precisa y segura sin cambiar el contexto de ejecución, sin depender de impersonaciones y sin debilitar el modelo de seguridad de la base de datos.

¿Cómo funciona la firma con certificados?

El procedimiento se basa en tres pasos fundamentales: creación del certificado, firma del procedimiento y asignación del permiso requerido a un login asociado al certificado. Cada uno de estos pasos es determinante para conseguir una delegación limpia y controlada.

Partimos de una situación donde un procedimiento necesita acceder a un objeto (por ejemplo, una tabla o vista), pero el usuario que lo ejecuta no tiene permiso directo sobre ese objeto. Esto normalmente no sería un problema salvo que el procedimiento y el objeto sean de un propietario distinto o que la consulta desde el procedimiento se ejecute en otro contexto de seguridad diferente, por ejemplo por usar sp_executesql. En estos casos, si firmamos el procedimiento con un certificado y asociamos el certificado a un login o usuario que sí tenga ese permiso, el procedimiento se ejecutará con esos privilegios, pero sin necesidad de cambiar el contexto de ejecución del usuario.

Caso práctico detallado

Supongamos que queremos que ciertos usuarios puedan ejecutar un procedimiento llamado usp_ObtenerVentasPrivadas, que accede a una tabla sensible como VentasPrivadas, pero sin darles acceso directo a dicha tabla. El procedimiento sería como el siguiente:

Tal como está, cualquier usuario que tenga permiso de ejecución sobre usp_ObtenerVentasPrivadas podrá acceder a VentasPrivadas, pero sin tener acceso directo a ella ni posibilidad de usarla fuera de este procedimiento. Esto es porque el contexto de ejecución sigue siendo el del usuario original al no tener código dinámico y al pertenecer la tabla y el procedimiento al mismo esquema (con el mismo owner del esquema).

Cuando la cosa se complica

Pero, ¿qué pasa si cambiamos el contexto de ejecución o tenemos distinto owner? Aquí es donde entran en juego los certificados. 

Yo para la demo que os pongo lo he simplificado y ejecuto una consulta simple pero, pongamos que tenemos un procedimiento que genera SQL dinámico y lo ejecuta con sp_executesql. Este sería el ejemplo que podeis reproducir vosotros mismos.

Como veis, si probamos a ejecutar el procedimiento almacenado con el usuario limitado va a dar error porque no tiene permisos sobre la tabla. Sin embargo, cuando creamos un certificado, lo asociamos a un usuario que tiene permisos sobre la tabla y firmamos el procedimiento con ese usuario ya podemos ejecutar sin error el procedimiento con el usuario limitado.

Ventajas clave de usar certificados

Una de las principales ventajas frente a EXECUTE AS es que el certificado no interfiere con el contexto de ejecución. Esto significa que si dentro del procedimiento hay llamadas a otros objetos que usan permisos del usuario original, todo seguirá funcionando correctamente. Además, los certificados son inmunes a problemas comunes como la pérdida de contexto entre bases de datos, lo que resulta útil en entornos distribuidos.

Otra ventaja es la auditabilidad. Como los permisos no se conceden directamente a los usuarios finales, sino que se encapsulan dentro de procedimientos firmados, es más sencillo identificar los puntos de entrada permitidos y realizar auditorías.

También se evita el problema clásico de los permisos residuales. Si un usuario necesita ejecutar varios procedimientos que requieren distintos permisos, no es necesario concederle un permiso amplio ni crear roles intermedios complejos. Firmamos cada procedimiento con los permisos justos que requiere, y el acceso queda perfectamente delimitado.

Consideraciones de los certificados a tener en cuenta

La firma de procedimientos con certificados no está exenta de ciertas limitaciones. Para empezar, no podemos firmar procedimientos encriptados ni CLR. Además, si un procedimiento se modifica, se pierde la firma y hay que volver a aplicarla.

En cuanto a la gestión de certificados, conviene centralizar su creación y almacenamiento de forma segura. El uso de contraseñas fuertes y una política clara de mantenimiento y renovación de certificados es fundamental para evitar riesgos.

Por último, es importante evitar el uso excesivo de esta técnica, especialmente si se convierte en la única forma de delegación. En bases de datos muy grandes o con cientos de procedimientos, puede ser más conveniente crear roles bien definidos y controlar el acceso de forma más clásica. La clave está en encontrar el equilibrio adecuado.

Comportamiento de DENY

Otra consideración muy importante que debemos tener en cuenta es que el uso de DENY no puede ser sobrepasado por una firma de certificado. Si el usuario que ejecuta el procedimiento tiene un DENY explícito sobre el objeto que se consulta dentro del procedimiento, nada podrá permitirle el acceso, por mucho que el certificado tenga ese GRANT.

Esto tiene dos consecuencias prácticas. La primera es que si queremos controlar el acceso mediante firmas, no debemos usar DENY. Sencillamente basta con no conceder permisos. Por otro lado, si existe un DENY, servirá como veto absoluto, incluso para accesos indirectos a través de procedimientos firmados.

Conclusión

Firmar procedimientos almacenados con certificados es una herramienta extremadamente útil y robusta para aplicar el principio de privilegios mínimos de forma segura y mantenible. Nos permite conceder permisos de forma encapsulada, sin necesidad de impersonaciones ni contextos elevados, y facilita una delegación precisa del acceso.

Es una técnica que deberíamos tener siempre en nuestro arsenal cuando diseñamos la arquitectura de seguridad de una base de datos SQL Server. En combinación con otras prácticas como el uso de roles, vistas seguras y auditoría de permisos, puede contribuir a sistemas mucho más sólidos, trazables y mantenibles.

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

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

Contextos de seguridad en SQL dinámico: permisos, procedimientos y sp_executesql

En mi artículo anterior os hablé de cómo construir SQL dinámico de forma segura usando sp_executesql, y cómo evitar riesgos como el SQL Injection. Sin embargo, hay un aspecto igual de crítico que no se suele tener en cuenta: el contexto de seguridad desde el que se ejecuta el código dinámico y su impacto en los permisos.

Muchos desarrolladores se sorprenden cuando un procedimiento almacenado que funciona con SQL «normal» deja de funcionar al pasar a SQL dinámico, a pesar de tener los mismos permisos. El motivo está en cómo SQL Server maneja los permisos de ejecución implícitos y qué ocurre cuando usamos sp_executesql. En este artículo voy a tratar de explicarlo paso a paso.

Permisos implícitos al ejecutar un procedimiento almacenado

Cuando concedemos a un usuario permiso para ejecutar un procedimiento almacenado, por ejemplo:

Ese usuario puede ejecutar el procedimiento sin necesidad de tener permisos directos sobre las tablas internas que use dicho procedimiento. Es decir, aunque no tenga SELECT sobre Sales.SalesOrderHeader, si el procedimiento ejecuta esta consulta:

… el usuario podrá obtener los datos. Esto ocurre porque el contexto de ejecución del procedimiento es el del propietario, y si el procedimiento y las tablas tienen el mismo dueño, SQL Server permite ese acceso mediante el mecanismo de ownership chaining (encadenamiento de propiedad). Este comportamiento es clave para encapsular la lógica de negocio sin exponer directamente las tablas subyacentes.

¿Qué ocurre con los permisos cuando usamos SQL dinámico?

Aquí viene la trampa. Si dentro del procedimiento usamos SQL dinámico con sp_executesql como:

… entonces se rompe la cadena de propiedad, y SQL Server evalúa los permisos como si el usuario estuviera ejecutando directamente la consulta, no como si formara parte del procedimiento. En otras palabras: aunque el usuario tenga permiso de ejecución sobre el procedimiento, necesitará permisos explícitos de SELECT sobre la tabla referenciada en el SQL dinámico.

Esto puede llevar a errores difíciles de diagnosticar si no se comprende cómo funciona el contexto de seguridad.

Demostración del problema de permisos

Imaginemos este escenario:

  • Usuario app_user
  • Procedimiento dbo.usp_Informe
  • Tabla Sales.SalesOrderHeader
  • Escenario app_user tiene EXECUTE sobre usp_Informe, pero no SELECT sobre la tabla
  • Código del procedimiento:

El primer SELECT se ejecuta correctamente gracias al ownership chaining. El segundo da error:

Cuándo se rompe y cuándo no

Para que se mantenga el ownership chaining, se deben cumplir dos condiciones:

  • Los objetos deben pertenecer al mismo propietario (normalmente dbo)
  • La consulta no debe usar SQL dinámico
  • En cuanto usamos EXEC, sp_executesql o EXECUTE AS con un contexto diferente, se interrumpe esa cadena y SQL Server valida los permisos del usuario directamente.

¿Cómo se soluciona el problema de permisos?

Existen varias estrategias según el contexto, pero las más comunes son estas:

1. Firmar el procedimiento con un certificado

Es la solución más profesional. Se firma el procedimiento con un certificado que tenga los permisos necesarios sobre las tablas, y el usuario ejecuta el procedimiento sin tener permisos directos. Requiere más trabajo, pero es la solución más segura y escalable, especialmente en entornos regulados o críticos.

2. Conceder permisos explícitos sobre las tablas

Es la opción más directa pero rompe el aislamiento que buscamos al encapsular la lógica dentro de procedimientos. Puede ser válido en entornos internos o controlados.

Pero en ese caso ya no protegemos las tablas detrás de la lógica del procedimiento.

3. Evitar usar SQL dinámico innecesariamente

Si el acceso a los datos no requiere construir partes dinámicas (columnas, nombres de tabla, filtros condicionales avanzados), es mejor mantener el SQL como texto plano. Así se conserva el contexto de permisos del procedimiento.

En ocasiones, usamos SQL dinámico cuando lo que realmente necesitamos es ejecutar una u otra consulta en función de un parámetro de entrada del procedimiento. En estos casos es mejor crear una lógica con IF que códigos dinámicos. 

Por ejemplo, este procedimiento con código SQL dinámico:

… lo podríamos sustituir por este:

Conclusión

El uso de SQL dinámico no es solo una cuestión de sintaxis o seguridad frente a inyecciones. También tiene implicaciones directas en el modelo de permisos y seguridad de SQL Server. Es importante entender que al usar sp_executesql, el procedimiento pierde la protección que le daba la cadena de propiedad, y el motor evalúa los permisos como si se tratase de una ejecución independiente. Este comportamiento puede ser confuso si no se conoce, pero una vez lo interiorizamos, se convierte en una herramienta poderosa para diseñar arquitecturas seguras y mantenibles. Si estamos diseñando procedimientos que deben proteger las tablas subyacentes, debemos considerar firmar con certificados, controlar cuidadosamente los permisos, o bien evitar SQL dinámico cuando sea posible.  

En el artículo anterior explicamos cómo generar SQL dinámico de forma segura desde el punto de vista sintáctico y de rendimiento. Pero ahora, sabemos que también hay que hacerlo de forma segura desde el punto de vista del contexto de ejecución.

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

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