Esta parece ser uma área com muitos mitos e visões conflitantes.
Então, qual é a diferença entre uma variável de tabela e uma tabela temporária local no SQL Server?
Esta parece ser uma área com muitos mitos e visões conflitantes.
Então, qual é a diferença entre uma variável de tabela e uma tabela temporária local no SQL Server?
Respostas:
Conteúdo
Embargo
Esta resposta discute variáveis de tabela "clássicas" introduzidas no SQL Server 2000. O SQL Server 2014 na memória OLTP apresenta os tipos de tabela com otimização de memória. As instâncias variáveis de tabela dessas são diferentes em muitos aspectos das discutidas abaixo! ( mais detalhes ).
Local de armazenamento
Não faz diferença. Ambos são armazenados em tempdb
.
Já vi sugerir que, para variáveis de tabela, esse nem sempre é o caso, mas isso pode ser verificado a seguir
DECLARE @T TABLE(X INT)
INSERT INTO @T VALUES(1),(2)
SELECT sys.fn_PhysLocFormatter(%%physloc%%) AS [File:Page:Slot]
FROM @T
Resultados de exemplo (mostrando a localização nas tempdb
2 linhas são armazenados)
File:Page:Slot
----------------
(1:148:0)
(1:148:1)
Localização Lógica
@table_variables
se comportam mais como se fizessem parte do banco de dados atual do que as #temp
tabelas. Para variáveis de tabela (desde 2005), os agrupamentos de colunas, se não especificados explicitamente, serão os do banco de dados atual, enquanto para as #temp
tabelas ele usará o agrupamento padrão de tempdb
( Mais detalhes ). Além disso, os tipos de dados definidos pelo usuário e as coleções XML devem estar no tempdb para serem usados nas #temp
tabelas, mas as variáveis da tabela podem usá-los no banco de dados atual ( Origem ).
O SQL Server 2012 apresenta bancos de dados contidos. o comportamento das tabelas temporárias nessas diferenças (h / t Aaron)
Em um banco de dados contido, os dados da tabela temporária são agrupados no agrupamento do banco de dados contido.
- Todos os metadados associados a tabelas temporárias (por exemplo, nomes de tabelas e colunas, índices etc.) estarão no agrupamento do catálogo.
- Restrições nomeadas não podem ser usadas em tabelas temporárias.
- As tabelas temporárias podem não se referir a tipos definidos pelo usuário, coleções de esquemas XML ou funções definidas pelo usuário.
Visibilidade para diferentes escopos
@table_variables
só podem ser acessados no lote e no escopo em que são declarados. #temp_tables
estão acessíveis em lotes filhos (gatilhos aninhados, procedimento, exec
chamadas). #temp_tables
criado no escopo externo ( @@NESTLEVEL=0
) também pode abranger lotes, pois persistem até o término da sessão. Nenhum tipo de objeto pode ser criado em um lote filho e acessado no escopo de chamada, como discutido a seguir ( podem ser encontradas ##temp
tabelas globais ).
Tempo de vida
@table_variables
são criados implicitamente quando um lote contendo uma DECLARE @.. TABLE
instrução é executada (antes da execução de qualquer código de usuário) e são descartados implicitamente no final.
Embora o analisador não permita que você tente usar a variável de tabela antes da DECLARE
instrução, a criação implícita pode ser vista abaixo.
IF (1 = 0)
BEGIN
DECLARE @T TABLE(X INT)
END
--Works fine
SELECT *
FROM @T
#temp_tables
são criados explicitamente quando a CREATE TABLE
instrução TSQL é encontrada e podem ser eliminados explicitamente com DROP TABLE
ou serão descartados implicitamente quando o lote termina (se criado em um lote filho com @@NESTLEVEL > 0
) ou quando a sessão termina de outra forma.
Nota: nas rotinas armazenadas, ambos os tipos de objetos podem ser armazenados em cache, em vez de criar e soltar repetidamente novas tabelas. Existem restrições sobre quando esse cache pode ocorrer, no entanto, é possível violar, #temp_tables
mas as restrições @table_variables
impedem de qualquer maneira. A sobrecarga de manutenção para #temp
tabelas em cache é um pouco maior que para as variáveis da tabela, conforme ilustrado aqui .
Metadados do objeto
Isso é essencialmente o mesmo para os dois tipos de objeto. Ele é armazenado nas tabelas base do sistema em tempdb
. No #temp
entanto, é mais simples ver uma tabela, pois ela OBJECT_ID('tempdb..#T')
pode ser usada para digitar as tabelas do sistema e o nome gerado internamente está mais correlacionado com o nome definido na CREATE TABLE
instrução. Para variáveis de tabela, a object_id
função não funciona e o nome interno é inteiramente gerado pelo sistema, sem relação com o nome da variável. A seguir, demonstramos que os metadados ainda estão lá, digitando um nome de coluna (possivelmente único). Para tabelas sem nomes de colunas exclusivos, o object_id pode ser determinado usando-se DBCC PAGE
desde que não estejam vazios.
/*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'
Resultado
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 |
+-----------+-----------+------+-------------------+-------------+------------+------------+-----------------------+
Transações
As operações @table_variables
são executadas como transações do sistema, independentemente de qualquer transação do usuário externo, enquanto as #temp
operações de tabela equivalentes são executadas como parte da própria transação do usuário. Por esse motivo, um ROLLBACK
comando afetará uma #temp
tabela, mas deixará o @table_variable
intocado.
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
Exploração madeireira
Ambos geram registros de log para o tempdb
log de transações. Um equívoco comum é que esse não é o caso de variáveis de tabela; portanto, um script demonstrando isso está abaixo; ele declara uma variável de tabela, adiciona algumas linhas, as atualiza e as exclui.
Como a variável da tabela é criada e eliminada implicitamente no início e no final do lote, é necessário usar vários lotes para ver o log 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
Devoluções
Tanto quanto eu sou capaz de discernir as operações em ambos, geramos quantidades aproximadamente iguais de log.
Embora a quantidade de log seja muito semelhante, uma diferença importante é que os registros de log relacionados às #temp
tabelas não podem ser limpos até que qualquer transação de usuário que contenha seja concluída, portanto, uma transação de longa execução que em algum momento grava em #temp
tabelas impedirá o truncamento de log, tempdb
enquanto as transações autônomas gerado para variáveis de tabela não.
As variáveis de tabela não suportam, TRUNCATE
portanto, podem estar em desvantagem de registro quando o requisito é remover todas as linhas de uma tabela (embora para tabelas muito pequenas DELETE
possam funcionar melhor de qualquer maneira )
Cardinalidade
Muitos dos planos de execução que envolvem variáveis de tabela mostrarão uma única linha estimada como a saída deles. A inspeção das propriedades da variável da tabela mostra que o SQL Server acredita que a variável da tabela possui zero linhas (por que ela estima que 1 linha será emitida a partir de uma tabela com zero linhas é explicada por @Paul White aqui ).
No entanto, os resultados mostrados na seção anterior mostram uma rows
contagem precisa em sys.partitions
. O problema é que, na maioria das vezes, as instruções que referenciam as variáveis da tabela são compiladas enquanto a tabela está vazia. Se a declaração for (re) compilada depois de @table_variable
preenchida, será usada para a cardinalidade da tabela (isso pode acontecer devido a um explícito recompile
ou talvez porque a declaração também faça referência a outro objeto que causa uma compilação adiada ou uma recompilação.)
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
O plano mostra a contagem precisa estimada de linhas após a compilação adiada.
No SQL Server 2012 SP2, o sinalizador de rastreamento 2453 é introduzido. Mais detalhes estão em "Relational Engine" aqui .
Quando esse sinalizador de rastreamento está ativado, pode fazer com que as recompilações automáticas levem em consideração a cardinalidade alterada, conforme discutido mais em breve.
Nota: No Azure, no nível de compatibilidade 150, a compilação da instrução agora é adiada até a primeira execução . Isso significa que ele não estará mais sujeito ao problema de estimativa de linha zero.
Nenhuma estatística de coluna
Ter uma cardinalidade de tabela mais precisa, no entanto, não significa que a contagem estimada de linhas seja mais precisa (a menos que seja realizada uma operação em todas as linhas da tabela). O SQL Server não mantém estatísticas de coluna para variáveis de tabela, portanto, recorrerá a estimativas baseadas no predicado de comparação (por exemplo, 10% da tabela será retornada para uma =
coluna não exclusiva ou 30% para uma >
comparação). Em contraste, as estatísticas da coluna são mantidas para #temp
tabelas.
O SQL Server mantém uma contagem do número de modificações feitas em cada coluna. Se o número de modificações desde a compilação do plano exceder o limite de recompilação (RT), o plano será recompilado e as estatísticas atualizadas. O RT depende do tipo e tamanho da tabela.
Do cache de plano no SQL Server 2008
RT é calculado da seguinte forma. (n se refere à cardinalidade de uma tabela quando um plano de consulta é compilado.)
Tabela permanente
- Se n <= 500, RT = 500.
- Se n> 500, RT = 500 + 0,20 * n.Tabela temporária
- Se n <6, RT = 6.
- Se 6 <= n <= 500, RT = 500.
- Se n> 500, RT = 500 + 0,20 * n.
Variável de tabela
- RT não existe. Portanto, as recompilações não acontecem devido a alterações nas cardinalidades das variáveis da tabela. (Mas veja a nota sobre o TF 2453 abaixo)
a KEEP PLAN
dica pode ser usada para definir o RT para #temp
tabelas da mesma forma que para tabelas permanentes.
O efeito líquido de tudo isso é que geralmente os planos de execução gerados para #temp
tabelas são melhores em magnitude do que @table_variables
quando há muitas linhas envolvidas, já que o SQL Server tem melhores informações para trabalhar.
NB1: As variáveis de tabela não possuem estatísticas, mas ainda podem incorrer em um evento de recompilação "Estatísticas Alteradas" no sinalizador de rastreamento 2453 (não se aplica a planos "triviais"). Isso parece ocorrer sob os mesmos limites de recompilação, como mostrado para as tabelas temporárias acima com um mais um que se N=0 -> RT = 1
. isto é, todas as instruções compiladas quando a variável da tabela está vazia acabam recebendo uma recompilação e são corrigidas TableCardinality
na primeira vez em que são executadas quando não estão vazias. A cardinalidade da tabela de tempos de compilação é armazenada no plano e se a instrução for executada novamente com a mesma cardinalidade (devido ao fluxo de instruções de controle ou à reutilização de um plano em cache), nenhuma recompilação ocorrerá.
NB2: Para tabelas temporárias em cache em procedimentos armazenados, a história de recompilação é muito mais complicada do que a descrita acima. Consulte Tabelas temporárias em procedimentos armazenados para obter todos os detalhes sangrentos.
Recompiles
Assim como as recompilações baseadas em modificação descritas acima, as #temp
tabelas também podem ser associadas a compilações adicionais, simplesmente porque elas permitem operações proibidas para variáveis de tabela que acionam uma compilação (por exemplo CREATE INDEX
, alterações de DDL ALTER TABLE
)
Travamento
Foi afirmado que as variáveis da tabela não participam do bloqueio. Este não é o caso. Executando as saídas abaixo na guia Mensagens do SSMS, os detalhes dos bloqueios obtidos e liberados para uma instrução de inserção.
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 consultas que, a SELECT
partir das variáveis da tabela, Paul White aponta nos comentários que elas vêm automaticamente com uma NOLOCK
dica implícita . Isso é mostrado abaixo
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 )
O impacto disso no bloqueio pode ser bem menor, no entanto.
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
Nenhum desses retornos resulta na ordem das chaves de índice, indicando que o SQL Server usou uma verificação ordenada de alocação para ambos.
Eu executei o script acima duas vezes e os resultados para a segunda execução estão abaixo
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
A saída de bloqueio para a variável da tabela é realmente extremamente mínima, pois o SQL Server apenas adquire um bloqueio de estabilidade do esquema no objeto. Mas, para uma #temp
mesa, é quase tão leve quanto é necessário um S
bloqueio no nível do objeto . Uma NOLOCK
dica ou READ UNCOMMITTED
nível de isolamento pode, naturalmente, ser especificado explicitamente quando se trabalha com #temp
mesas bem.
Da mesma forma que o problema com o log de uma transação do usuário ao redor, isso pode significar que os bloqueios são mantidos por mais tempo nas #temp
tabelas. Com o script abaixo
--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
Quando executado fora de uma transação explícita do usuário nos dois casos, o único bloqueio retornado ao verificar sys.dm_tran_locks
é um bloqueio compartilhado no DATABASE
.
Ao descomentar, as BEGIN TRAN ... ROLLBACK
26 linhas são retornadas, mostrando que os bloqueios são mantidos no próprio objeto e nas linhas da tabela do sistema para permitir a reversão e impedir que outras transações leiam dados não confirmados. A operação de variável de tabela equivalente não está sujeita a reversão com a transação do usuário e não precisa reter esses bloqueios para que possamos verificar na próxima instrução, mas os bloqueios de rastreamento adquiridos e liberados no Profiler ou usando o sinalizador de rastreamento 1200 mostram muitos eventos de bloqueio ainda ocorrer.
Índices
Para versões anteriores ao SQL Server 2014, os índices só podem ser criados implicitamente nas variáveis da tabela como efeito colateral da adição de uma restrição exclusiva ou chave primária. Obviamente, isso significa que apenas índices únicos são suportados. Um índice não clusterizado não exclusivo em uma tabela com um índice clusterizado exclusivo pode ser simulado, simplesmente declarando-o UNIQUE NONCLUSTERED
e adicionando a chave CI ao final da chave NCI desejada (o SQL Server faria isso nos bastidores de qualquer maneira, mesmo se um não exclusivo NCI pode ser especificado)
Como demonstrado anteriormente, vários index_option
s podem ser especificados na declaração de restrição DATA_COMPRESSION
, incluindo,, IGNORE_DUP_KEY
e FILLFACTOR
(embora não haja sentido em defini-lo, pois isso só faria diferença na reconstrução do índice e você não pode reconstruir índices nas variáveis da tabela!)
Além disso, as variáveis de tabela não suportam INCLUDE
d colunas, índices filtrados (até 2016) ou particionamento, mas as #temp
tabelas (o esquema de partição deve ser criado tempdb
).
Índices no SQL Server 2014
Índices não exclusivos podem ser declarados embutidos na definição de variável da tabela no SQL Server 2014. Exemplo de sintaxe para isso é mostrado abaixo.
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 no SQL Server 2016
A partir do CTP 3.1, agora é possível declarar índices filtrados para variáveis de tabela. Por RTM, pode ser que as colunas incluídas também sejam permitidas, embora elas provavelmente não entrem no SQL16 devido a restrições de recursos
DECLARE @T TABLE
(
c1 INT NULL INDEX ix UNIQUE WHERE c1 IS NOT NULL /*Unique ignoring nulls*/
)
Paralelismo
As consultas inseridas (ou modificadas) @table_variables
não podem ter um plano paralelo, #temp_tables
não são restritas dessa maneira.
Existe uma solução alternativa aparente nessa reescrita, da seguinte forma, permitindo que a SELECT
peça ocorra paralelamente, mas isso acaba usando uma tabela temporária oculta (nos bastidores)
INSERT INTO @DATA ( ... )
EXEC('SELECT .. FROM ...')
Não existe tal limitação nas consultas que selecionam as variáveis da tabela, conforme ilustrado na minha resposta aqui
Outras diferenças funcionais
#temp_tables
não pode ser usado dentro de uma função. @table_variables
pode ser usado dentro de UDFs escalares ou de tabelas com várias instruções.@table_variables
não pode ter restrições nomeadas.@table_variables
não pode ser SELECT
-ed INTO
, ALTER
-ed, TRUNCATE
d ou ser o destino de DBCC
comandos como DBCC CHECKIDENT
ou de SET IDENTITY INSERT
e não suporta dicas de tabela comoWITH (FORCESCAN)
CHECK
as restrições nas variáveis da tabela não são consideradas pelo otimizador para simplificação, predicados implícitos ou detecção de contradições.PAGELATCH_EX
espera. ( Exemplo )Apenas memória?
Conforme declarado no início, ambos são armazenados nas páginas da tempdb
. No entanto, não resolvi se havia alguma diferença de comportamento quando se trata de gravar essas páginas em disco.
Eu fiz uma pequena quantidade de testes sobre isso agora e até agora não vi essa diferença. No teste específico que fiz na minha instância do SQL Server 250 páginas, parece ser o ponto de corte antes que o arquivo de dados seja gravado.
OBSERVAÇÃO: O comportamento abaixo não ocorre mais no SQL Server 2014 ou no SQL Server 2012 SP1 / CU10 ou SP2 / CU1; o gravador ansioso não está mais tão ansioso para gravar páginas no disco. Mais detalhes sobre essa alteração no SQL Server 2014: tempdb Hidden Performance Gem .
Executando o script abaixo
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
E o monitoramento de gravações no tempdb
arquivo de dados com o Process Monitor não vi nenhum (exceto ocasionalmente na página de inicialização do banco de dados no deslocamento 73.728). Depois de mudar 250
para 251
eu comecei a ver as gravações abaixo.
A captura de tela acima mostra gravações de 5 * 32 páginas e uma única página, indicando que 161 das páginas foram gravadas no disco. Também obtive o mesmo ponto de corte de 250 páginas ao testar com variáveis de tabela. O script abaixo mostra uma maneira diferente, observandosys.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 192 páginas foram gravadas no disco e o sinalizador sujo foi limpo. Também mostra que a gravação em disco não significa que as páginas serão removidas do buffer pool imediatamente. As consultas nessa variável de tabela ainda podem ser satisfeitas inteiramente da memória.
Em um servidor inativo com páginas de max server memory
conjunto de buffers definidas 2000 MB
e DBCC MEMORYSTATUS
relatadas como aproximadamente 1.843.000 KB (c. 23.000 páginas), inseri as tabelas acima em lotes de 1.000 linhas / páginas e para cada iteração registrada.
SELECT Count(*)
FROM sys.dm_os_buffer_descriptors
WHERE database_id = 2
AND allocation_unit_id = @allocId
AND page_type = 'DATA_PAGE'
Tanto a variável da tabela quanto a #temp
tabela forneceram gráficos quase idênticos e conseguiram maximizar o buffer pool antes de chegar ao ponto em que elas não estavam totalmente armazenadas na memória, de modo que não parece haver nenhuma limitação específica quanto à quantidade de memória qualquer um pode consumir.
Gostaria de salientar algumas coisas com base mais em experiências particulares do que em estudos. Como DBA, sou muito novo, por isso, corrija-me quando necessário.