¿Cuál es la diferencia entre una tabla temporal y una variable de tabla en SQL Server?

Respuestas:

668

Contenido

Contenido

Consideración

Esta respuesta analiza las variables de tabla "clásicas" introducidas en SQL Server 2000. SQL Server 2014 en memoria OLTP presenta los tipos de tabla con memoria optimizada. ¡Las instancias de variables de tabla de esas son diferentes en muchos aspectos a las que se analizan a continuación! ( más detalles )

Ubicación de almacenamiento

Ninguna diferencia. Ambos se almacenan en tempdb.

He visto que sugiere que para las variables de tabla esto no siempre es el caso, pero esto se puede verificar a continuación.

DECLARE @T TABLE(X INT)

INSERT INTO @T VALUES(1),(2)

SELECT sys.fn_PhysLocFormatter(%%physloc%%) AS [File:Page:Slot]
FROM @T

Resultados de ejemplo ( tempdbse almacenan las ubicaciones que muestran las 2 filas)

File:Page:Slot
----------------
(1:148:0)
(1:148:1)

Ubicación lógica

@table_variablesse comportan más como si fueran parte de la base de datos actual que las #temptablas. Para las variables de tabla (desde 2005), las intercalaciones de columnas si no se especifican explícitamente serán las de la base de datos actual, mientras que para las #temptablas utilizarán la intercalación predeterminada de tempdb( Más detalles ). Además, los tipos de datos definidos por el usuario y las colecciones XML deben estar en tempdb para usar en las #temptablas, pero las variables de tabla pueden usarlos desde la base de datos actual ( Fuente ).

SQL Server 2012 presenta bases de datos contenidas. El comportamiento de las tablas temporales en estos difiere (h / t Aaron)

En una base de datos contenida, los datos de la tabla temporal se recopilan en la clasificación de la base de datos contenida.

  • Todos los metadatos asociados con tablas temporales (por ejemplo, nombres de tablas y columnas, índices, etc.) estarán en la clasificación del catálogo.
  • Las restricciones con nombre no se pueden usar en tablas temporales.
  • Las tablas temporales pueden no referirse a tipos definidos por el usuario, colecciones de esquemas XML o funciones definidas por el usuario.

Visibilidad a diferentes ámbitos

@table_variablessolo se puede acceder dentro del lote y el alcance en el que se declaran. #temp_tablesson accesibles dentro de lotes secundarios (disparadores anidados, procedimiento, execllamadas). #temp_tablescreado en el ámbito externo ( @@NESTLEVEL=0) también puede abarcar lotes a medida que persisten hasta que finaliza la sesión. Sin embargo, no se puede crear ningún tipo de objeto en un lote secundario y acceder a él en el alcance de la llamada, como se discute a continuación (sin embargo, las ##temptablas globales pueden serlo).

Toda la vida

@table_variablesse crean implícitamente cuando DECLARE @.. TABLEse ejecuta un lote que contiene una instrucción (antes de que se ejecute cualquier código de usuario en ese lote) y se eliminan implícitamente al final.

Aunque el analizador no le permitirá probar y usar la variable de tabla antes de la DECLAREdeclaración, la creación implícita se puede ver a continuación.

IF (1 = 0)
BEGIN
DECLARE @T TABLE(X INT)
END

--Works fine
SELECT *
FROM @T

#temp_tablesse crean explícitamente cuando CREATE TABLEse encuentra la instrucción TSQL y se pueden descartar explícitamente DROP TABLEo se descartarán implícitamente cuando finalice el lote (si se creó en un lote secundario con @@NESTLEVEL > 0) o cuando la sesión finalice de otra manera.

NB: Dentro de las rutinas almacenadas, ambos tipos de objetos se pueden almacenar en caché en lugar de crear y soltar tablas nuevas repetidamente. Sin embargo, existen restricciones sobre cuándo puede ocurrir este almacenamiento en caché que es posible violar #temp_tablespero que las restricciones @table_variablesimpiden de todos modos. La sobrecarga de mantenimiento para las #temptablas en caché es ligeramente mayor que para las variables de tabla como se ilustra aquí .

Metadatos de objeto

Esto es esencialmente lo mismo para ambos tipos de objeto. Se almacena en las tablas base del sistema en tempdb. Sin #tempembargo, es más sencillo ver una tabla, ya que OBJECT_ID('tempdb..#T')se puede usar para ingresar las tablas del sistema y el nombre generado internamente está más estrechamente relacionado con el nombre definido en la CREATE TABLEdeclaración. Para las variables de tabla, la object_idfunción no funciona y el nombre interno se genera completamente por el sistema sin relación con el nombre de la variable. Sin embargo, a continuación se muestra que los metadatos todavía están allí al ingresar un nombre de columna (con suerte único). Para las tablas sin nombres de columna únicos, el object_id se puede determinar usando DBCC PAGEsiempre que no estén vacíos.

/*Declare a table variable with some unusual options.*/
DECLARE @T TABLE
(
[dba.se] INT IDENTITY PRIMARY KEY NONCLUSTERED,
A INT CHECK (A > 0),
B INT DEFAULT 1,
InRowFiller char(1000) DEFAULT REPLICATE('A',1000),
OffRowFiller varchar(8000) DEFAULT REPLICATE('B',8000),
LOBFiller varchar(max) DEFAULT REPLICATE(cast('C' as varchar(max)),10000),
UNIQUE CLUSTERED (A,B) 
    WITH (FILLFACTOR = 80, 
         IGNORE_DUP_KEY = ON, 
         DATA_COMPRESSION = PAGE, 
         ALLOW_ROW_LOCKS=ON, 
         ALLOW_PAGE_LOCKS=ON)
)

INSERT INTO @T (A)
VALUES (1),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13)

SELECT t.object_id,
       t.name,
       p.rows,
       a.type_desc,
       a.total_pages,
       a.used_pages,
       a.data_pages,
       p.data_compression_desc
FROM   tempdb.sys.partitions AS p
       INNER JOIN tempdb.sys.system_internals_allocation_units AS a
         ON p.hobt_id = a.container_id
       INNER JOIN tempdb.sys.tables AS t
         ON t.object_id = p.object_id
       INNER JOIN tempdb.sys.columns AS c
         ON c.object_id = p.object_id
WHERE  c.name = 'dba.se'

Salida

Duplicate key was ignored.

 +-----------+-----------+------+-------------------+-------------+------------+------------+-----------------------+
| object_id |   name    | rows |     type_desc     | total_pages | used_pages | data_pages | data_compression_desc |
+-----------+-----------+------+-------------------+-------------+------------+------------+-----------------------+
| 574625090 | #22401542 |   13 | IN_ROW_DATA       |           2 |          2 |          1 | PAGE                  |
| 574625090 | #22401542 |   13 | LOB_DATA          |          24 |         19 |          0 | PAGE                  |
| 574625090 | #22401542 |   13 | ROW_OVERFLOW_DATA |          16 |         14 |          0 | PAGE                  |
| 574625090 | #22401542 |   13 | IN_ROW_DATA       |           2 |          2 |          1 | NONE                  |
+-----------+-----------+------+-------------------+-------------+------------+------------+-----------------------+

Actas

Las operaciones @table_variablesse llevan a cabo como transacciones del sistema, independientemente de cualquier transacción de usuario externo, mientras que las #tempoperaciones de tabla equivalentes se llevarían a cabo como parte de la transacción del usuario en sí. Por esta razón, un ROLLBACKcomando afectará a una #temptabla pero dejará @table_variableintacta la tabla .

DECLARE @T TABLE(X INT)
CREATE TABLE #T(X INT)

BEGIN TRAN

INSERT #T
OUTPUT INSERTED.X INTO @T
VALUES(1),(2),(3)

/*Both have 3 rows*/
SELECT * FROM #T
SELECT * FROM @T

ROLLBACK

/*Only table variable now has rows*/
SELECT * FROM #T
SELECT * FROM @T
DROP TABLE #T

Inicio sesión

Ambos generan registros de anotaciones en el registro de tempdbtransacciones. Una idea errónea común es que este no es el caso para las variables de tabla, por lo que a continuación se muestra un script que demuestra esto, declara una variable de tabla, agrega un par de filas, luego las actualiza y las elimina.

Debido a que la variable de la tabla se crea y se elimina implícitamente al inicio y al final del lote, es necesario usar varios lotes para ver el registro completo.

USE tempdb;

/*
Don't run this on a busy server.
Ideally should be no concurrent activity at all
*/
CHECKPOINT;

GO

/*
The 2nd column is binary to allow easier correlation with log output shown later*/
DECLARE @T TABLE ([C71ACF0B-47E9-4CAD-9A1E-0C687A8F9CF3] INT, B BINARY(10))

INSERT INTO @T
VALUES (1, 0x41414141414141414141), 
       (2, 0x41414141414141414141)

UPDATE @T
SET    B = 0x42424242424242424242

DELETE FROM @T

/*Put allocation_unit_id into CONTEXT_INFO to access in next batch*/
DECLARE @allocId BIGINT, @Context_Info VARBINARY(128)

SELECT @Context_Info = allocation_unit_id,
       @allocId = a.allocation_unit_id 
FROM   sys.system_internals_allocation_units a
       INNER JOIN sys.partitions p
         ON p.hobt_id = a.container_id
       INNER JOIN sys.columns c
         ON c.object_id = p.object_id
WHERE  ( c.name = 'C71ACF0B-47E9-4CAD-9A1E-0C687A8F9CF3' )

SET CONTEXT_INFO @Context_Info

/*Check log for records related to modifications of table variable itself*/
SELECT Operation,
       Context,
       AllocUnitName,
       [RowLog Contents 0],
       [Log Record Length]
FROM   fn_dblog(NULL, NULL)
WHERE  AllocUnitId = @allocId

GO

/*Check total log usage including updates against system tables*/
DECLARE @allocId BIGINT = CAST(CONTEXT_INFO() AS BINARY(8));

WITH T
     AS (SELECT Operation,
                Context,
                CASE
                  WHEN AllocUnitId = @allocId THEN 'Table Variable'
                  WHEN AllocUnitName LIKE 'sys.%' THEN 'System Base Table'
                  ELSE AllocUnitName
                END AS AllocUnitName,
                [Log Record Length]
         FROM   fn_dblog(NULL, NULL) AS D)
SELECT Operation = CASE
                     WHEN GROUPING(Operation) = 1 THEN 'Total'
                     ELSE Operation
                   END,
       Context,
       AllocUnitName,
       [Size in Bytes] = COALESCE(SUM([Log Record Length]), 0),
       Cnt = COUNT(*)
FROM   T
GROUP  BY GROUPING SETS( ( Operation, Context, AllocUnitName ), ( ) )
ORDER  BY GROUPING(Operation),
          AllocUnitName 

Devoluciones

Vista detallada

Captura de pantalla de resultados

Vista de resumen (incluye el registro de la caída implícita y las tablas base del sistema)

Captura de pantalla de resultados

Por lo que he podido discernir, las operaciones en ambos generan cantidades aproximadamente iguales de registro.

Si bien la cantidad de registros es muy similar, una diferencia importante es que los registros de registros relacionados con las #temptablas no se pueden borrar hasta que finalice cualquier transacción de usuario que contenga, por lo que una transacción de larga duración que en algún momento escriba en las #temptablas evitará el truncamiento del registro, tempdbmientras que las transacciones autónomas generado para las variables de tabla no.

Las variables de tabla no son compatibles, TRUNCATEpor lo que puede estar en desventaja de registro cuando el requisito es eliminar todas las filas de una tabla (aunque para tablas muy pequeñas DELETE puede funcionar mejor de todos modos )

Cardinalidad

Muchos de los planes de ejecución que involucran variables de tabla mostrarán una sola fila estimada como la salida de ellos. La inspección de las propiedades de la variable de tabla muestra que SQL Server cree que la variable de la tabla tiene cero filas ( aquí @Paul White explica por qué estima que se emitirá 1 fila de una tabla de cero filas ).

Sin embargo, los resultados mostrados en la sección anterior muestran un rowsrecuento preciso en sys.partitions. El problema es que en la mayoría de las ocasiones las declaraciones que hacen referencia a las variables de la tabla se compilan mientras la tabla está vacía. Si la declaración se (re) compila después de haber @table_variablesido poblada, se utilizará para la cardinalidad de la tabla (esto podría suceder debido a una recompiledeclaración explícita o quizás porque la declaración también hace referencia a otro objeto que causa una compilación diferida o una recompilación).

DECLARE @T TABLE(I INT);

INSERT INTO @T VALUES(1),(2),(3),(4),(5)

CREATE TABLE #T(I INT)

/*Reference to #T means this statement is subject to deferred compile*/
SELECT * FROM @T WHERE NOT EXISTS(SELECT * FROM #T)

DROP TABLE #T

El plan muestra el recuento de filas estimado exacto después de la compilación diferida.

Muestra el recuento exacto de filas

En SQL Server 2012 SP2, se introduce el indicador de traza 2453. Más detalles están en "Motor relacional" aquí .

Cuando este indicador de rastreo está habilitado, puede hacer que las recompilaciones automáticas tengan en cuenta la cardinalidad modificada, como se explica más adelante en breve.

NB: en Azure en el nivel de compatibilidad 150, la compilación de la declaración ahora se aplaza hasta la primera ejecución . Esto significa que ya no estará sujeto al problema de estimación de fila cero.

No hay estadísticas de columna

Sin embargo, tener una cardinalidad de tabla más precisa no significa que el recuento de filas estimado sea más preciso (a menos que se realice una operación en todas las filas de la tabla). SQL Server no mantiene estadísticas de columna para las variables de la tabla, por lo que recurrirá a las suposiciones basadas en el predicado de comparación (por ejemplo, que el 10% de la tabla se devolverá para una =columna no exclusiva o el 30% para una >comparación). En contraste, las estadísticas de columna se mantienen para las #temptablas.

SQL Server mantiene un recuento del número de modificaciones realizadas en cada columna. Si el número de modificaciones desde que se compiló el plan supera el umbral de recompilación (RT), el plan se volverá a compilar y se actualizarán las estadísticas. El RT depende del tipo y tamaño de la tabla.

Del almacenamiento en caché del plan en SQL Server 2008

RT se calcula de la siguiente manera. (n se refiere a la cardinalidad de una tabla cuando se compila un plan de consulta).

Tabla permanente
- Si n <= 500, RT = 500.
- Si n> 500, RT = 500 + 0.20 * n.

Tabla temporal
- Si n <6, RT = 6.
- Si 6 <= n <= 500, RT = 500.
- Si n> 500, RT = 500 + 0.20 * n.
Variable de tabla
: RT no existe. Por lo tanto, las recompilaciones no ocurren debido a cambios en las cardinalidades de las variables de la tabla. (Pero vea la nota sobre TF 2453 a continuación)

la KEEP PLANsugerencia se puede usar para establecer el RT para #temptablas igual que para tablas permanentes.

El efecto neto de todo esto es que, a menudo, los planes de ejecución generados para las #temptablas son de órdenes de magnitud mejor que @table_variablescuando intervienen muchas filas, ya que SQL Server tiene mejor información para trabajar.

NB1: Las variables de tabla no tienen estadísticas, pero aún pueden incurrir en un evento de recompilación "Estadísticas modificadas" bajo la marca de seguimiento 2453 (no se aplica a planes "triviales") Esto parece ocurrir bajo los mismos umbrales de recompilación que se muestran para las tablas temporales anteriores con un uno adicional que si N=0 -> RT = 1. es decir, todas las declaraciones compiladas cuando la variable de la tabla está vacía terminarán obteniendo una recompilación y corregidas TableCardinalityla primera vez que se ejecutan cuando no están vacías. La cardinalidad de la tabla de tiempos de compilación se almacena en el plan y si la declaración se ejecuta nuevamente con la misma cardinalidad (ya sea debido a las declaraciones de flujo de control o la reutilización de un plan en caché) no se produce la recompilación.

NB2: Para las tablas temporales almacenadas en caché en procedimientos almacenados, la historia de compilación es mucho más complicada que la descrita anteriormente. Ver Tablas temporales en procedimientos almacenados para todos los detalles sangrientos.

Recompila

Además de las recompilaciones basadas en modificaciones descritas anteriormente, las #temptablas también pueden asociarse con compilaciones adicionales simplemente porque permiten operaciones que están prohibidas para las variables de tabla que desencadenan una compilación (por ejemplo CREATE INDEX, cambios DDL ALTER TABLE)

Cierre

Se ha dicho que las variables de tabla no participan en el bloqueo. Este no es el caso. Al ejecutar los siguientes resultados en la pestaña de mensajes SSMS, se detallan los bloqueos tomados y liberados para una instrucción de inserción.

DECLARE @tv_target TABLE (c11 int, c22 char(100))

DBCC TRACEON(1200,-1,3604)

INSERT INTO @tv_target (c11, c22)

VALUES (1, REPLICATE('A',100)), (2, REPLICATE('A',100))

DBCC TRACEOFF(1200,-1,3604)

Para las consultas que SELECTde las variables de la tabla, Paul White señala en los comentarios que estas vienen automáticamente con una NOLOCKpista implícita . Esto se muestra a continuación

DECLARE @T TABLE(X INT); 

SELECT X
FROM @T 
OPTION (RECOMPILE, QUERYTRACEON 3604, QUERYTRACEON 8607)

Salida

*** Output Tree: (trivial plan) ***

        PhyOp_TableScan TBL: @T Bmk ( Bmk1000) IsRow: COL: IsBaseRow1002  Hints( NOLOCK )

Sin embargo, el impacto de esto en el bloqueo podría ser bastante menor.

SET NOCOUNT ON;

CREATE TABLE #T( [ID] [int] IDENTITY NOT NULL,
                 [Filler] [char](8000) NULL,
                 PRIMARY KEY CLUSTERED ([ID] DESC))


DECLARE @T TABLE ( [ID] [int] IDENTITY NOT NULL,
                 [Filler] [char](8000) NULL,
                 PRIMARY KEY CLUSTERED ([ID] DESC))

DECLARE @I INT = 0

WHILE (@I < 10000)
BEGIN
INSERT INTO #T DEFAULT VALUES
INSERT INTO @T DEFAULT VALUES
SET @I += 1
END

/*Run once so compilation output doesn't appear in lock output*/
EXEC('SELECT *, sys.fn_PhysLocFormatter(%%physloc%%) FROM #T')

DBCC TRACEON(1200,3604,-1)
SELECT *, sys.fn_PhysLocFormatter(%%physloc%%)
FROM @T

PRINT '--*--'

EXEC('SELECT *, sys.fn_PhysLocFormatter(%%physloc%%) FROM #T')

DBCC TRACEOFF(1200,3604,-1)

DROP TABLE #T

Ninguno de estos resultados devuelve un orden de clave de índice que indica que SQL Server utilizó una exploración ordenada por asignación para ambos.

Ejecuté el script anterior dos veces y los resultados para la segunda ejecución están debajo

Process 58 acquiring Sch-S lock on OBJECT: 2:-1325894110:0  (class bit0 ref1) result: OK

--*--
Process 58 acquiring IS lock on OBJECT: 2:-1293893996:0  (class bit0 ref1) result: OK

Process 58 acquiring S lock on OBJECT: 2:-1293893996:0  (class bit0 ref1) result: OK

Process 58 releasing lock on OBJECT: 2:-1293893996:0 

La salida de bloqueo para la variable de tabla es realmente mínima ya que SQL Server simplemente adquiere un bloqueo de estabilidad de esquema en el objeto. Pero para una #tempmesa es casi tan liviano ya que saca un Sbloqueo de nivel de objeto . Por supuesto, también se puede especificar explícitamente una NOLOCKpista o un READ UNCOMMITTEDnivel de aislamiento cuando se trabaja con #temptablas.

De manera similar al problema con el registro de una transacción de usuario circundante, puede significar que los bloqueos se mantengan más tiempo para las #temptablas. Con el guión a continuación

    --BEGIN TRAN;   

    CREATE TABLE #T (X INT,Y CHAR(4000) NULL);

    INSERT INTO #T (X) VALUES(1) 

    SELECT CASE resource_type
             WHEN  'OBJECT' THEN OBJECT_NAME(resource_associated_entity_id, 2)
             WHEN  'ALLOCATION_UNIT' THEN (SELECT OBJECT_NAME(object_id, 2)
                                           FROM  tempdb.sys.allocation_units a 
                                           JOIN tempdb.sys.partitions p ON a.container_id = p.hobt_id
                                           WHERE  a.allocation_unit_id = resource_associated_entity_id)
             WHEN 'DATABASE' THEN DB_NAME(resource_database_id)                                      
             ELSE (SELECT OBJECT_NAME(object_id, 2)
                   FROM   tempdb.sys.partitions
                   WHERE  partition_id = resource_associated_entity_id)
           END AS object_name,
           *
    FROM   sys.dm_tran_locks
    WHERE  request_session_id = @@SPID

    DROP TABLE #T

   -- ROLLBACK  

cuando se ejecuta fuera de una transacción de usuario explícita para ambos casos, el único bloqueo devuelto cuando se verifica sys.dm_tran_lockses un bloqueo compartido en DATABASE.

Al descomentar las BEGIN TRAN ... ROLLBACK26 filas se devuelven mostrando que los bloqueos se mantienen tanto en el objeto como en las filas de la tabla del sistema para permitir la reversión y evitar que otras transacciones lean datos no confirmados. La operación de variable de tabla equivalente no está sujeta a reversión con la transacción del usuario y no tiene necesidad de mantener estos bloqueos para que podamos verificar en la siguiente declaración, pero los bloqueos de rastreo adquiridos y liberados en Profiler o usando el indicador de rastreo 1200 muestran que todavía hay muchos eventos de bloqueo ocurrir.

Índices

Para las versiones anteriores a SQL Server 2014, los índices solo se pueden crear implícitamente en las variables de la tabla como efecto secundario de agregar una restricción única o clave primaria. Por supuesto, esto significa que solo se admiten índices únicos. Sin embargo, se puede simular un índice no agrupado no único en una tabla con un índice agrupado único simplemente declarándolo UNIQUE NONCLUSTEREDy agregando la clave CI al final de la clave NCI deseada (SQL Server haría esto detrás de escena de todos modos, incluso si no fuera único NCI podría especificarse)

Como se demostró anteriormente, index_optionse pueden especificar varios s en la declaración de restricción DATA_COMPRESSION, incluidos , IGNORE_DUP_KEYy FILLFACTOR(aunque no tiene sentido establecerlo, ya que solo haría una diferencia en la reconstrucción del índice y no se pueden reconstruir índices en las variables de la tabla).

Además, las variables de tabla no admiten INCLUDEcolumnas d, índices filtrados (hasta 2016) o particiones, las #temptablas sí (el esquema de partición debe crearse en tempdb).

Índices en SQL Server 2014

Los índices no únicos se pueden declarar en línea en la definición de variable de tabla en SQL Server 2014. A continuación se muestra una sintaxis de ejemplo.

DECLARE @T TABLE (
C1 INT INDEX IX1 CLUSTERED, /*Single column indexes can be declared next to the column*/
C2 INT INDEX IX2 NONCLUSTERED,
       INDEX IX3 NONCLUSTERED(C1,C2) /*Example composite index*/
);

Índices en SQL Server 2016

Desde CTP 3.1 ahora es posible declarar índices filtrados para variables de tabla. Por RTM puede darse el caso de que las columnas incluidas también estén permitidas, aunque es probable que no lleguen a SQL16 debido a limitaciones de recursos

DECLARE @T TABLE
(
c1 INT NULL INDEX ix UNIQUE WHERE c1 IS NOT NULL /*Unique ignoring nulls*/
)

Paralelismo

Las consultas que se insertan (o modifican) @table_variablesno pueden tener un plan paralelo, #temp_tablesno están restringidas de esta manera.

Existe una solución alternativa aparente en que la reescritura de la siguiente manera permite que la SELECTparte tenga lugar en paralelo, pero eso termina usando una tabla temporal oculta (detrás de escena)

INSERT INTO @DATA ( ... ) 
EXEC('SELECT .. FROM ...')

No existe tal limitación en las consultas que seleccionan de las variables de la tabla como se ilustra en mi respuesta aquí

Otras diferencias funcionales

  • #temp_tablesno se puede usar dentro de una función. @table_variablesse puede usar dentro de UDF de tabla escalar o de múltiples instrucciones.
  • @table_variables no puede tener restricciones con nombre.
  • @table_variablesno puede ser SELECT-ed INTO, ALTER-ed, TRUNCATEdo ser el objetivo de DBCCcomandos como DBCC CHECKIDENTo of SET IDENTITY INSERTy no admite sugerencias de tabla comoWITH (FORCESCAN)
  • CHECK El optimizador no considera las restricciones en las variables de la tabla para la simplificación, los predicados implícitos o la detección de contradicciones.
  • Las variables de tabla no parecen calificar para la optimización de intercambio de conjuntos de filas, lo que significa que eliminar y actualizar los planes en contra de estos puede encontrar más gastos generales y PAGELATCH_EXesperas. ( Ejemplo )

¿Solo memoria?

Como se indicó al principio, ambos se almacenan en páginas en tempdb. Sin embargo, no mencioné si hubo alguna diferencia en el comportamiento cuando se trata de escribir estas páginas en el disco.

He hecho una pequeña cantidad de pruebas en esto ahora y hasta ahora no he visto tal diferencia. En la prueba específica que hice en mi instancia de SQL Server, 250 páginas parecen ser el punto de corte antes de que se escriba el archivo de datos.

NB: el siguiente comportamiento ya no se produce en SQL Server 2014 o SQL Server 2012 SP1 / CU10 o SP2 / CU1, el escritor ansioso ya no está tan ansioso por escribir páginas en el disco. Más detalles sobre ese cambio en SQL Server 2014: tempdb Hidden Performance Gem .

Ejecutando el siguiente script

CREATE TABLE #T(X INT, Filler char(8000) NULL)
INSERT INTO #T(X)
SELECT TOP 250 ROW_NUMBER() OVER (ORDER BY @@SPID)
FROM master..spt_values
DROP TABLE #T

Y el monitoreo escribe en el tempdbarchivo de datos con Process Monitor. No vi ninguno (excepto ocasionalmente en la página de inicio de la base de datos en el desplazamiento 73,728). Después de cambiar 250a 251Comencé a ver escrituras como a continuación.

ProcMon

La captura de pantalla de arriba muestra 5 * 32 páginas escritas y una sola página que indica que 161 de las páginas fueron escritas en el disco. Obtuve el mismo punto de corte de 250 páginas cuando probé con variables de tabla también. El siguiente script muestra una forma diferente al mirarsys.dm_os_buffer_descriptors

DECLARE @T TABLE (
  X        INT,
  [dba.se] CHAR(8000) NULL)

INSERT INTO @T
            (X)
SELECT TOP 251 Row_number() OVER (ORDER BY (SELECT 0))
FROM   master..spt_values

SELECT is_modified,
       Count(*) AS page_count
FROM   sys.dm_os_buffer_descriptors
WHERE  database_id = 2
       AND allocation_unit_id = (SELECT a.allocation_unit_id
                                 FROM   tempdb.sys.partitions AS p
                               INNER JOIN tempdb.sys.system_internals_allocation_units AS a
                                          ON p.hobt_id = a.container_id
                                        INNER JOIN tempdb.sys.columns AS c
                                          ON c.object_id = p.object_id
                                 WHERE  c.name = 'dba.se')
GROUP  BY is_modified 

Resultados

is_modified page_count
----------- -----------
0           192
1           61

Mostrando que se escribieron 192 páginas en el disco y se borró la bandera sucia. También muestra que estar escrito en el disco no significa que las páginas serán expulsadas del grupo de búferes inmediatamente. Las consultas contra esta variable de tabla aún podrían satisfacerse por completo de la memoria.

En un servidor inactivo con max server memoryconjunto 2000 MBe DBCC MEMORYSTATUSinformes de Buffer Pool Páginas asignadas como aproximadamente 1,843,000 KB (c. 23,000 páginas) inserté en las tablas anteriores en lotes de 1,000 filas / páginas y para cada iteración registrada.

SELECT Count(*)
FROM   sys.dm_os_buffer_descriptors
WHERE  database_id = 2
       AND allocation_unit_id = @allocId
       AND page_type = 'DATA_PAGE' 

Tanto la variable de tabla como la #temptabla dieron gráficos casi idénticos y lograron maximizar el grupo de búferes antes de llegar al punto de que no estaban completamente almacenados en la memoria, por lo que no parece haber ninguna limitación particular en la cantidad de memoria cualquiera puede consumir.

Páginas en el Buffer Pool

Martin Smith
fuente
Descubrí que SQL Server adquiere significativamente más bloqueos al crear tablas temporales (incluso con almacenamiento en caché) en comparación con una variable de tabla equivalente. Puede probar utilizando el XE de depuración latch_acquired y creando una tabla con aproximadamente 35 columnas más o menos. Encontré la variable de la tabla para tomar 4 pestillos y la tabla temporal para tomar alrededor de 70 pestillos.
Joe Obbish
40

Hay algunas cosas que me gustaría señalar basadas más en experiencias particulares en lugar de estudiar. Como DBA, soy muy nuevo, así que corríjame donde sea necesario.

  1. Las tablas #temp de forma predeterminada usan la clasificación predeterminada de la instancia de SQL Server. Por lo tanto, a menos que se especifique lo contrario, es posible que tenga problemas para comparar o actualizar valores entre las tablas #temp y las tablas de la base de datos, si el masterdb tiene una clasificación diferente de la base de datos. Ver: http://www.mssqltips.com/sqlservertip/2440/create-sql-server-temporary-tables-with-the-correct-collation/
  2. Completamente basado en la experiencia personal, la memoria disponible parece tener un efecto sobre el que funciona mejor. MSDN recomienda usar variables de tabla para almacenar conjuntos de resultados más pequeños, pero la mayoría de las veces la diferencia ni siquiera se nota. Sin embargo, en conjuntos más grandes, es obvio en algunos casos que las variables de la tabla requieren mucha más memoria y pueden ralentizar la consulta.
Kahn
fuente
66
También tenga en cuenta que la clasificación en las tablas #temp puede heredar la clasificación de la base de datos que realiza la llamada si está utilizando SQL Server 2012 y la base de datos está contenida.
Aaron Bertrand
Aclaración sobre # 2 para conjuntos pequeños vs grandes stackoverflow.com/a/14465163/5224021
GibralterTop