Tablas temporales en el SQL Server

Introducción

En el mundo de las bases de datos es muy común la utilización de tablas temporales. A pesar de que todo el mundo sabe que este tipo de estructuras ralentizan el funcionamiento de nuestras consultas, los programadores no pueden evitar recurrir a ellas porque muchas veces facilitan la resolución de problemas. Almacenar datos para usarlos posteriormente, guardar resultados parciales, analizar grandes cantidades de filas. Hay muchos casos en los que podemos necesitar estas tablas temporales, ¡Pero hay que utilizarlas correctamente! Primer consejo: no usar tablas temporales

El primer consejo que tenemos que seguir a la hora de trabajar con tablas temporales es bien sencillo: no usarlas. ¿Y por qué no? Pues hay un montón de razones que iremos viendo a lo largo de este texto, pero para empezar veamos en que se traduce el utilizar una tabla temporal en SQL Server:

  • Las tablas temporales se crean en tempdb, y al crearlas se producen varios bloqueos sobre esta base de datos como por ejemplo en las tablas sysobjects y sysindex. Los bloqueos sobre tempdb afectan a todo el servidor.
  • Al crearlas es necesario que se realicen accesos de escritura al disco ( no siempre si las tablas son pequeñas)
  • Al introducir datos en las tablas temporales de nuevo se produce actividad en el disco, y ya sabemos que el acceso a disco suele ser el "cuello de botella" de nuestro sistema
  • Al leer datos de la tabla temporal hay que recurrir de nuevo al disco. Además estos datos leídos de la tabla suelen combinarse con otros.
  • Al borrar la tabla de nuevo hay que adquirir bloqueos sobre la base de datos tempdb y realizar operaciones en disco.
  • Al usar tablas temporales dentro de un procedimiento almacenado perdemos la ventaja de tener compilado el plan de ejecución de dicho procedimiento almacenado y se producirán recompilaciones más a menudo. Lo mismo pasará cuando el SQL Server intenta reutilizar el plan de ejecución de una consulta parametrizada. Si en la consulta tenemos una tabla temporal difícilmente se reutilizará dicho plan de ejecución.

Vistos estos problemas creo que no hace falta repetir nuestro prime consejo. ¿Y qué podemos hacer entonces?

En vez de tablas temporales podemos mejorar nuestro código para que no sean necesarias, podemos usar subconsultas (normalmente usar una subconsulta mejora drásticamente el rendimiento respecto a usar tablas temporales), usar tablas permanentes, usar tablas derivadas.

Hay que recordar siempre que cualquier alternativa es buena si evitamos usar tablas temporales (¡cursores excluidos por supuesto!)

De todos modos si alguna vez tenemos que usarlas es mejor conocerlas bien, así que vamos a ello. Tipos de tablas temporales

Las tablas temporales son de dos tipos en cuanto al alcance la tabla. Tenemos tablas temporales locales y tablas temporales globales.

#locales: Las tablas temporales locales tienen una # como primer carácter en su nombre y sólo se pueden utilizar en la conexión en la que el usuario las crea. Cuando la conexión termina la tabla temporal desaparece.

##globales Las tablas temporales globales comienzan con ## y son visibles por cualquier usuario conectado al SQL Server. Y una cosa más, estás tablas desaparecen cuando ningún usuario está haciendo referencias a ellas, no cuado se desconecta el usuario que la creo.

Temp Realmente hay un tipo más de tablas temporales. Si creamos una tabla dentro de la base de datos temp es una tabla real en cuanto a que podemos utilizarla como cualquier otra tabla en cualquier base de datos, y es temporal en cuanto a que desaparece en cuanto apagamos el servidor.

Funcionamiento de tablas temporales

Crear una tabla temporal es igual que crear una tabla normal. Veámoslo con un ejemplo:

CREATE TABLE #TablaTemporal (Campo1 int, Campo2 varchar(50))

Y se usan de manera habitual.

INSERT INTO #TalbaTemporal VALUES (1,'Primer campo')
INSERT INTO #TalbaTemporal VALUES (2,'Segundo campo')
SELECT * FROM #TablaTemporal

Como vemos no hay prácticamente limitaciones a la hora de trabajar con tablas temporales (una limitación es que no pueden tener restricciones FOREING KEY). Optimizar el uso de tablas temporales

El uso que les podemos dar a este tipo de tablas es infinito, pero siempre teniendo en cuenta unas cuantas directivas que debemos seguir para que ralenticen nuestro trabajo lo menos posible.

Por ejemplo no es mala costumbre crear las tablas temporales con comandos DDL como en el ejemplo anterior (CREATE TABLE) y luego rellenarlas comandos INSERT o con INSERT INTO. Es cierto que eso mismo lo podemos lograr en un único paso con SELECT INTO, pero esta opción es peor porque los bloqueos que se adquieren sobre objetos del sistema duran más tiempo.

Como siempre es mejor pedir los campos que queremos y no poner el típico SELCT * FROM... De la misma manera es muy recomendable cualificar las filas que queremos y no tener filas que no vamos a utilizar en tablas temporales.

Otra buena costumbre es borrar nosotros nuestras tablas. Sí que es cierto que al acabar la conexión las tablas temporales locales desaparecen, pero si tenemos un conjunto de sentencias largo y creamos una tabla temporal al principio y no la vamos a utilizar en el resto del tiempo no tiene sentido tener esa tabla ahí ocupando espacio y memoria.

Si las tablas temporales son grandes una opción para aumentar el rendimiento es crear un índice que nos ayude a recuperar los datos de esa tabla (para tablas pequeñas es un gasto inútil porque nunca se usarán los índices).

Colocar la base de datos tempdb en un disco dedicado solo para esta función aumentará el rendimiento global del sistema si se hace un uso intensivo de tablas temporales.

Y por último pero no menos importante, no creéis tablas temporales dentro de transacciones ni dentro de triggers. creedme que la concurrencia de vuestra base de datos sufrirá mucho si lo hacéis. Variables de tabla

Con SQL Server 2000 podemos declarar variables de tipo table. Este tipo de variables tienen una serie de ventajas sobre las tablas temporales por lo que siempre que podamos escogeremos usar variables de tabla frente a tablas temporales. Usar variables temporales es sencillo:

DECLARE @VariableTabla TABLE (Campo1 int, Campo2 char(50))
INSERT INTO @VariableTabla VALUES (1,'Primer campo')
INSERT INTO @VariableTabla VALUES (2,'Segundo campo')
SELECT * FROM @VariableTabla

Ventajas que encontraremos al usar variables de tipo tabla:

  • Tienen un ámbito bien definido. El procedimiento almacenado, la función o el batch en el que se declaran.
  • Las variables de tipo tabla producen menos recompilaciones de los procedimientos almacenados en los que se encuentran que si utilizamos tablas temporales.
  • Las variables de tabla no necesitan de bloqueos ni de tantos recursos como las tablas temporales.

Pero también tienen inconvenientes:

  • No podemos cambiar la definición de la tabla una vez declarada
  • No podemos utilizar índices que no sean agrupados
  • No se pueden utilizar en INSERT INTO ni en SELECT INTO
  • No podemos utilizar funciones en las restricciones

Si ponemos en una balanza las ventajas y los inconvenientes vemos que en general es mejor utilizar las variables de tipo tabla que las tablas temporales. Solo en el caso de tener gran cantidad de datos en una tabla temporal y si la vamos a usar varias veces es preferible la opción de tablas temporales porque en ellas podemos definir índices. Un ejemplo

Todo esto está muy bien, pero como siempre lo mejor es ver un ejemplo en el que podamos ver que merece la pena el esfuerzo de reprogramar nuestro código para no usar tablas temporales.

Vamos a ver un ejemplo simple y alejado de la realidad pero que ilustre lo que queremos explicar en este texto. Vamos a utilizar la base de datos Northwind.

En esta base de datos los pedidos se envían a través de tres compañías de trasnportes: Speedy Express(1), United Package(2) y Federal Shipping(3). La compañía Federal Shipping nos oferta realizar todos los envíos que hacemos a través de United Package al precio fijo de 10$.

Decidimos que este ahorro merece la pena y vamos a cambiar en nuestra base de datos todos los pedidos abiertos que tienen que ser enviados por United Package para que sean enviados a través de Federal Shipping.

Para hacer esta actualización de los datos tenemos varias opciones. Vamos a comparar tres formas de hacerlo.

Metodo 1: Tablas temporales

declare @st datetime
SET @st =getdate()
CREATE TABLE #Actualizar (OrderId int, ShipVia int, Freight money)
INSERT INTO #Actualizar SELECT OrderID, ShipVia, Freight  
	FROM Orders WHERE ShipVia=2 AND ShippedDate IS NULL
UPDATE Orders SET ShipVia=3, Freight=10 WHERE OrderID IN   
	(SELECT OrderID FROM #Actualizar)DROP TABLE #Actualizar
PRINT 'Operacion completada en: '  + RTRIM(cast(datediff(ms,@st,getdate()) as char(10)))  
	+ ' milisegundos'

Y obtenemos como resultado:

(11 filas afectadas)
(11 filas afectadas)
Operacion completada en: 140 milisegundos

Metodo 1: Variables tipo Tabla

DECLARE @st datetime
SET @st =getdate() 
DECLARE @Actualizar TABLE(OrderId int, ShipVia int, Freight money)
INSERT INTO @Actualizar SELECT OrderID, ShipVia, Freight 
	FROM Orders WHERE ShipVia=2 AND ShippedDate IS NULL
UPDATE Orders SET ShipVia=3, Freight=10 WHERE OrderID IN   
	(SELECT OrderID FROM @Actualizar)
PRINT 'Operacion completada en: '   + rtrim(cast(datediff(ms,@st,getdate()) AS char(10)))   
	+ ' milisegundos'

Y en este caso el resultado es:

(11 filas afectadas)
(11 filas afectadas)
Operacion completada en: 73 milisegundos

Metodo 1: Sin Tablas temporales

DECLARE @st datetime
SET @st =getdate()
UPDATE Orders SET ShipVia=3, Freight=10 WHERE OrderID IN   
	(SELECT OrderID FROM Orders WHERE ShipVia=2 AND ShippedDate IS NULL)
PRINT 'Operacion completada en: '   + rtrim(cast(datediff(ms,@st,getdate()) AS char(10)))   
	+ ' milisegundos'

Y por último obtenemos:

(11 filas afectadas)
Operacion completada en: 50 milisegundos

Desde luego este ejemplo no es significativo, y en cada caso hay que estudiar la situación y comparar los resultados obtenidos en un entorno de trabajo para saber cual es la mejor opción, pero de todos modos espero que esto os sirva al menos para conocer un poco mejor a las "tablas temporales".

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP
ARTÍCULO ANTERIOR