Esta parece ser un área con bastantes mitos y puntos de vista conflictivos.
Entonces, ¿cuál es la diferencia entre una variable de tabla y una tabla temporal local en SQL Server?
Esta parece ser un área con bastantes mitos y puntos de vista conflictivos.
Entonces, ¿cuál es la diferencia entre una variable de tabla y una tabla temporal local en SQL Server?
Respuestas:
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


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.

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)
*** 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.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.

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
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.

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.