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 ( tempdb
se almacenan las ubicaciones que muestran las 2 filas)
File:Page:Slot
----------------
(1:148:0)
(1:148:1)
Ubicación lógica
@table_variables
se comportan más como si fueran parte de la base de datos actual que las #temp
tablas. 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 #temp
tablas 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 #temp
tablas, 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_variables
solo se puede acceder dentro del lote y el alcance en el que se declaran. #temp_tables
son accesibles dentro de lotes secundarios (disparadores anidados, procedimiento, exec
llamadas). #temp_tables
creado 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 ##temp
tablas globales pueden serlo).
Toda la vida
@table_variables
se crean implícitamente cuando DECLARE @.. TABLE
se 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 DECLARE
declaració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_tables
se crean explícitamente cuando CREATE TABLE
se encuentra la instrucción TSQL y se pueden descartar explícitamente DROP TABLE
o 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_tables
pero que las restricciones @table_variables
impiden de todos modos. La sobrecarga de mantenimiento para las #temp
tablas 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 #temp
embargo, 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 TABLE
declaración. Para las variables de tabla, la object_id
funció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 PAGE
siempre 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_variables
se llevan a cabo como transacciones del sistema, independientemente de cualquier transacción de usuario externo, mientras que las #temp
operaciones de tabla equivalentes se llevarían a cabo como parte de la transacción del usuario en sí. Por esta razón, un ROLLBACK
comando afectará a una #temp
tabla pero dejará @table_variable
intacta 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 tempdb
transacciones. 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 #temp
tablas 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 #temp
tablas evitará el truncamiento del registro, tempdb
mientras que las transacciones autónomas generado para las variables de tabla no.
Las variables de tabla no son compatibles, TRUNCATE
por 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 rows
recuento 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_variable
sido poblada, se utilizará para la cardinalidad de la tabla (esto podría suceder debido a una recompile
declaració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 #temp
tablas.
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 PLAN
sugerencia se puede usar para establecer el RT para #temp
tablas igual que para tablas permanentes.
El efecto neto de todo esto es que, a menudo, los planes de ejecución generados para las #temp
tablas son de órdenes de magnitud mejor que @table_variables
cuando 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 TableCardinality
la 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 #temp
tablas 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 SELECT
de las variables de la tabla, Paul White señala en los comentarios que estas vienen automáticamente con una NOLOCK
pista 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 #temp
mesa es casi tan liviano ya que saca un S
bloqueo de nivel de objeto . Por supuesto, también se puede especificar explícitamente una NOLOCK
pista o un READ UNCOMMITTED
nivel de aislamiento cuando se trabaja con #temp
tablas.
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 #temp
tablas. 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_locks
es un bloqueo compartido en DATABASE
.
Al descomentar las BEGIN TRAN ... ROLLBACK
26 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 NONCLUSTERED
y 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_option
se pueden especificar varios s en la declaración de restricción DATA_COMPRESSION
, incluidos , IGNORE_DUP_KEY
y 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 INCLUDE
columnas d, índices filtrados (hasta 2016) o particiones, las #temp
tablas 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_variables
no pueden tener un plan paralelo, #temp_tables
no están restringidas de esta manera.
Existe una solución alternativa aparente en que la reescritura de la siguiente manera permite que la SELECT
parte 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_tables
no se puede usar dentro de una función. @table_variables
se puede usar dentro de UDF de tabla escalar o de múltiples instrucciones.@table_variables
no puede tener restricciones con nombre.@table_variables
no puede ser SELECT
-ed INTO
, ALTER
-ed, TRUNCATE
do ser el objetivo de DBCC
comandos como DBCC CHECKIDENT
o of SET IDENTITY INSERT
y 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_EX
esperas. ( 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 tempdb
archivo 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 250
a 251
Comencé 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 memory
conjunto 2000 MB
e DBCC MEMORYSTATUS
informes 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 #temp
tabla 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.