Qual é a diferença entre uma tabela temporária e uma variável de tabela no SQL Server?

Respostas:

668

Conteúdo

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 tempdb2 linhas são armazenados)

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

Localização Lógica

@table_variablesse comportam mais como se fizessem parte do banco de dados atual do que as #temptabelas. 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 #temptabelas 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 #temptabelas, 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_variablessó podem ser acessados ​​no lote e no escopo em que são declarados. #temp_tablesestão acessíveis em lotes filhos (gatilhos aninhados, procedimento, execchamadas). #temp_tablescriado 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 ##temptabelas globais ).

Tempo de vida

@table_variablessão criados implicitamente quando um lote contendo uma DECLARE @.. TABLEinstruçã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 DECLAREinstruçã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_tablessão criados explicitamente quando a CREATE TABLEinstrução TSQL é encontrada e podem ser eliminados explicitamente com DROP TABLEou 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_tablesmas as restrições @table_variablesimpedem de qualquer maneira. A sobrecarga de manutenção para #temptabelas 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 #tempentanto, é 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 TABLEinstrução. Para variáveis ​​de tabela, a object_idfunçã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 PAGEdesde 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_variablessão executadas como transações do sistema, independentemente de qualquer transação do usuário externo, enquanto as #tempoperações de tabela equivalentes são executadas como parte da própria transação do usuário. Por esse motivo, um ROLLBACKcomando afetará uma #temptabela, mas deixará o @table_variableintocado.

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

Visão detalhada

Captura de tela dos resultados

Visualização de resumo (inclui o log para queda implícita e tabelas base do sistema)

Captura de tela dos resultados

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 #temptabelas 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 #temptabelas impedirá o truncamento de log, tempdbenquanto as transações autônomas gerado para variáveis ​​de tabela não.

As variáveis ​​de tabela não suportam, TRUNCATEportanto, 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 rowscontagem 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_variablepreenchida, será usada para a cardinalidade da tabela (isso pode acontecer devido a um explícito recompileou 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.

Mostra a contagem precisa de linhas

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

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 PLANdica pode ser usada para definir o RT para #temptabelas da mesma forma que para tabelas permanentes.

O efeito líquido de tudo isso é que geralmente os planos de execução gerados para #temptabelas são melhores em magnitude do que @table_variablesquando 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 TableCardinalityna 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 #temptabelas 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 SELECTpartir das variáveis ​​da tabela, Paul White aponta nos comentários que elas vêm automaticamente com uma NOLOCKdica implícita . Isso é mostrado abaixo

DECLARE @T TABLE(X INT); 

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

Resultado

*** 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 #tempmesa, é quase tão leve quanto é necessário um Sbloqueio no nível do objeto . Uma NOLOCKdica ou READ UNCOMMITTEDnível de isolamento pode, naturalmente, ser especificado explicitamente quando se trabalha com #tempmesas 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 #temptabelas. 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 ... ROLLBACK26 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 NONCLUSTEREDe 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_options podem ser especificados na declaração de restrição DATA_COMPRESSION, incluindo,, IGNORE_DUP_KEYe 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 INCLUDEd colunas, índices filtrados (até 2016) ou particionamento, mas as #temptabelas (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_variablesnão podem ter um plano paralelo, #temp_tablesnão são restritas dessa maneira.

Existe uma solução alternativa aparente nessa reescrita, da seguinte forma, permitindo que a SELECTpeç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_tablesnão pode ser usado dentro de uma função. @table_variablespode ser usado dentro de UDFs escalares ou de tabelas com várias instruções.
  • @table_variables não pode ter restrições nomeadas.
  • @table_variablesnão pode ser SELECT-ed INTO, ALTER-ed, TRUNCATEd ou ser o destino de DBCCcomandos como DBCC CHECKIDENTou de SET IDENTITY INSERTe 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.
  • As variáveis ​​da tabela parecem não se qualificar para a otimização do compartilhamento do conjunto de linhas, o que significa que excluir e atualizar planos com relação a eles pode encontrar mais sobrecarga e PAGELATCH_EXespera. ( 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 tempdbarquivo 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 250para 251eu comecei a ver as gravações abaixo.

ProcMon

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 

Resultados

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 memoryconjunto de buffers definidas 2000 MBe DBCC MEMORYSTATUSrelatadas 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 #temptabela 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.

Páginas no buffer pool

Martin Smith
fonte
Descobri que o SQL Server adquire significativamente mais travas ao criar tabelas temporárias (mesmo com cache) em comparação com uma variável de tabela equivalente. Você pode testar usando o latch_acquired debug XE e criando uma tabela com cerca de 35 colunas ou mais. Eu encontrei a variável de tabela com 4 travas e a tabela temporária com algo em torno de 70 travas.
Joe Obbish
40

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.

  1. As tabelas #temp por padrão usam o agrupamento padrão da instância do SQL Server. Portanto, a menos que seja especificado de outra forma, você pode ter problemas ao comparar ou atualizar valores entre as #temp tables e as tabelas do banco de dados, se o masterdb tiver um agrupamento diferente do banco de dados. Consulte: http://www.mssqltips.com/sqlservertip/2440/create-sql-server-temporary-tables-with-the-correct-collation/
  2. Completamente baseada na experiência pessoal, a memória disponível parece ter um efeito sobre o desempenho melhor. O MSDN recomenda o uso de variáveis ​​de tabela para armazenar conjuntos de resultados menores, mas na maioria das vezes a diferença nem é perceptível. Em conjuntos maiores, no entanto, tornou-se óbvio em alguns casos que as variáveis ​​da tabela consomem muito mais memória e podem atrasar a consulta para um rastreamento.
Kahn
fonte
6
Observe também que o agrupamento nas tabelas #temp pode herdar o agrupamento do banco de dados de chamada se você estiver usando o SQL Server 2012 e o banco de dados estiver contido.
Aaron Bertrand
Esclarecimento sobre # 2 para pequenas vs grandes conjuntos stackoverflow.com/a/14465163/5224021
GibralterTop