Qual è la differenza tra una tabella temporanea e una variabile di tabella in SQL Server?


Risposte:


668

Contenuto

Contenuto

Avvertimento

Questa risposta discute le variabili di tabella "classiche" introdotte in SQL Server 2000. SQL Server 2014 in memoria OLTP introduce tipi di tabelle ottimizzate per la memoria. Le istanze variabili di tabella di queste sono diverse per molti aspetti rispetto a quelle discusse di seguito! ( maggiori dettagli ).

Posizione di archiviazione

Nessuna differenza. Entrambi sono memorizzati in tempdb.

Ho visto suggerire che per le variabili di tabella questo non è sempre il caso, ma questo può essere verificato dal basso

DECLARE @T TABLE(X INT)

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

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

Risultati di esempio (che mostra la posizione nelle tempdb2 righe sono memorizzati)

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

Posizione logica

@table_variablescomportarsi più come se facessero parte del database corrente rispetto alle #temptabelle. Per le variabili di tabella (dal 2005) le regole di confronto delle colonne, se non specificate in modo esplicito, saranno quelle del database corrente, mentre per le #temptabelle utilizzerà le regole di confronto predefinite di tempdb( Maggiori dettagli ). Inoltre, i tipi di dati definiti dall'utente e le raccolte XML devono essere in tempdb da utilizzare per le #temptabelle, ma le variabili di tabella possono utilizzarle dal database corrente ( Origine ).

SQL Server 2012 introduce database contenuti. il comportamento delle tabelle temporanee in questi differisce (h / t Aaron)

In un database contenuto i dati della tabella temporanea vengono raccolti nelle regole di confronto del database contenuto.

  • Tutti i metadati associati alle tabelle temporanee (ad esempio nomi di tabelle e colonne, indici e così via) saranno inclusi nelle regole di confronto del catalogo.
  • I vincoli con nome non possono essere utilizzati nelle tabelle temporanee.
  • Le tabelle temporanee non possono fare riferimento a tipi definiti dall'utente, raccolte di schemi XML o funzioni definite dall'utente.

Visibilità a diversi ambiti

@table_variablesè possibile accedere solo all'interno del batch e nell'ambito in cui sono dichiarati. #temp_tablessono accessibili all'interno di batch secondari (trigger nidificati, procedura, execchiamate). #temp_tablescreato nell'ambito esterno ( @@NESTLEVEL=0) può estendersi anche ai batch poiché persistono fino al termine della sessione. Nessuno dei due tipi di oggetto può essere creato in un batch figlio e accessibile nell'ambito di chiamata, tuttavia, come discusso in seguito (è possibile tuttavia utilizzare le##temp tabelle globali ).

Tutta la vita

@table_variablesvengono creati implicitamente quando DECLARE @.. TABLEviene eseguito un batch contenente un'istruzione (prima che venga eseguito qualsiasi codice utente in quel batch) e vengono rilasciati implicitamente alla fine.

Sebbene il parser non ti consenta di provare a utilizzare la variabile di tabella prima DECLAREdell'istruzione, la creazione implicita può essere vista sotto.

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

--Works fine
SELECT *
FROM @T

#temp_tablesvengono creati in modo esplicito quando CREATE TABLEviene rilevata l' istruzione TSQL e possono essere rilasciati in modo esplicito con DROP TABLEo verranno eliminati in modo implicito quando il batch termina (se creato in un batch figlio con @@NESTLEVEL > 0) o quando la sessione termina in altro modo.

NB: All'interno delle routine memorizzate è possibile memorizzare nella cache entrambi i tipi di oggetto anziché creare e rilasciare ripetutamente nuove tabelle. Ci sono restrizioni su quando può verificarsi questa memorizzazione nella cache, tuttavia che è possibile violare #temp_tablesma che le restrizioni su @table_variablesimpediscono comunque. Il sovraccarico di manutenzione per le #temptabelle memorizzate nella cache è leggermente maggiore rispetto alle variabili della tabella, come illustrato qui .

Metadati oggetto

Questo è essenzialmente lo stesso per entrambi i tipi di oggetto. È memorizzato nelle tabelle di base del sistema in tempdb. È più semplice vedere una #temptabella, tuttavia, poiché OBJECT_ID('tempdb..#T')può essere utilizzata per digitare le tabelle di sistema e il nome generato internamente è più strettamente correlato al nome definito CREATE TABLEnell'istruzione. Per le variabili di tabella la object_idfunzione non funziona e il nome interno è interamente generato dal sistema senza alcuna relazione con il nome della variabile. Quanto segue dimostra che i metadati sono ancora presenti digitando un nome di colonna (si spera univoco). Per le tabelle senza nomi di colonna univoci, object_id può essere determinato usando DBCC PAGEpurché non siano vuoti.

/*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'

Produzione

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

Le transazioni

Le operazioni su @table_variablesvengono eseguite come transazioni di sistema, indipendentemente da qualsiasi transazione dell'utente esterno, mentre le #tempoperazioni della tabella equivalente verrebbero eseguite come parte della transazione dell'utente stesso. Per questo motivo un ROLLBACKcomando influenzerà una #temptabella ma lascerà @table_variableintatto.

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

Registrazione

Entrambi generano record di registro nel registro delle tempdbtransazioni. Un malinteso comune è che questo non è il caso delle variabili di tabella, quindi uno script che dimostra questo è di seguito, dichiara una variabile di tabella, aggiunge un paio di righe, quindi le aggiorna e le elimina.

Poiché la variabile della tabella viene creata e rilasciata in modo implicito all'inizio e alla fine del batch, è necessario utilizzare più batch per visualizzare la registrazione completa.

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 

ritorna

Vista dettagliata

Schermata dei risultati

Vista di riepilogo (include la registrazione per il rilascio implicito e le tabelle di base del sistema)

Schermata dei risultati

Per quanto sono stato in grado di discernere le operazioni su entrambi generano quantità approssimativamente uguali di registrazione.

Mentre la quantità di registrazione è molto simile, una differenza importante è che i record di registro relativi alle #temptabelle non possono essere cancellati fino a quando una transazione dell'utente contenente non termina così una transazione di lunga durata che a un certo punto scrive sulle #temptabelle impedirà il troncamento del registro tempdbmentre le transazioni autonome generato per le variabili di tabella no.

Le variabili di tabella non supportano TRUNCATEquindi possono essere in svantaggio di registrazione quando il requisito è rimuovere tutte le righe da una tabella (anche se per tabelle molto piccole DELETE può comunque funzionare meglio )

Cardinalità

Molti dei piani di esecuzione che coinvolgono variabili di tabella mostreranno una singola riga stimata come output da essi. L'ispezione delle proprietà della variabile di tabella mostra che SQL Server ritiene che la variabile di tabella abbia zero righe (Perché stima che una riga verrà emessa da una tabella di zero righe è spiegata da @Paul White qui ).

Tuttavia, i risultati mostrati nella sezione precedente mostrano un rowsconteggio accurato in sys.partitions. Il problema è che nella maggior parte dei casi le istruzioni che fanno riferimento alle variabili della tabella vengono compilate mentre la tabella è vuota. Se l'istruzione viene (ri) compilata dopo che @table_variableè stata compilata, questa verrà invece utilizzata per la cardinalità della tabella (ciò potrebbe accadere a causa di un esplicito recompileo forse perché l'istruzione fa riferimento anche a un altro oggetto che provoca una compilazione posticipata o una ricompilazione).

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

Il piano mostra un conteggio di righe stimato accurato dopo la compilazione differita.

Mostra il conteggio accurato delle righe

In SQL Server 2012 SP2, è stato introdotto il flag di traccia 2453. Maggiori dettagli sono disponibili in "Motore relazionale" qui .

Quando questo flag di tracciamento è abilitato, è possibile che i ricompilamenti automatici tengano conto della cardinalità modificata, come discusso ulteriormente a breve.

NB: In Azure nel livello di compatibilità 150 la compilazione dell'istruzione è ora rinviata alla prima esecuzione . Ciò significa che non sarà più soggetto al problema di stima della riga zero.

Nessuna statistica di colonna

Avere una cardinalità della tabella più accurata non significa tuttavia che il conteggio delle righe stimato sarà più accurato (a meno che non si esegua un'operazione su tutte le righe della tabella). SQL Server non mantiene affatto le statistiche di colonna per le variabili di tabella, quindi ricadrà su ipotesi basate sul predicato del confronto (ad esempio, il 10% della tabella verrà restituito per una =colonna non univoca o il 30% per un >confronto). Al contrario, le statistiche delle colonne vengono mantenute per le #temptabelle.

SQL Server mantiene un conteggio del numero di modifiche apportate a ciascuna colonna. Se il numero di modifiche da quando il piano è stato compilato supera la soglia di ricompilazione (RT), il piano verrà ricompilato e le statistiche aggiornate. RT dipende dal tipo e dalle dimensioni della tabella.

Dal piano di memorizzazione nella cache in SQL Server 2008

RT è calcolato come segue. (n si riferisce alla cardinalità di una tabella quando viene compilato un piano di query.)

Tabella permanente
- Se n <= 500, RT = 500.
- Se n> 500, RT = 500 + 0,20 * n.

Tabella temporanea
- Se n <6, RT = 6.
- Se 6 <= n <= 500, RT = 500.
- Se n> 500, RT = 500 + 0,20 * n.
Variabile di tabella
- RT non esiste. Pertanto, non si verificano ricompilazioni a causa di cambiamenti nelle cardinalità delle variabili di tabella. (Ma vedi nota su TF 2453 di seguito)

il KEEP PLANsuggerimento può essere usato per impostare RT per le #temptabelle come per le tabelle permanenti.

L'effetto netto di tutto ciò è che spesso i piani di esecuzione generati per le #temptabelle sono ordini di grandezza migliori rispetto a @table_variablesquando sono coinvolte molte righe poiché SQL Server ha informazioni migliori con cui lavorare.

NB1: Le variabili di tabella non dispongono di statistiche ma possono comunque presentare un evento di ricompilazione "Statistiche modificate" sotto il flag di traccia 2453 (non si applica ai piani "banali") Ciò sembra verificarsi sotto le stesse soglie di ricompilazione mostrate per le tabelle temporanee sopra con un uno aggiuntivo che se N=0 -> RT = 1. cioè tutte le istruzioni compilate quando la variabile della tabella è vuota finiranno per ottenere una ricompilazione e corrette TableCardinalityla prima volta che vengono eseguite quando non sono vuote. La cardinalità della tabella dei tempi di compilazione viene archiviata nel piano e se l'istruzione viene eseguita nuovamente con la stessa cardinalità (a causa del flusso di istruzioni di controllo o del riutilizzo di un piano memorizzato nella cache) non si verifica alcuna ricompilazione.

NB2: per le tabelle temporanee memorizzate nella cache nelle stored procedure, la storia della ricompilazione è molto più complicata di quanto sopra descritto. Vedere le tabelle temporanee nelle procedure memorizzate per tutti i dettagli gory.

ricompilazioni

Oltre alle ricompilazioni basate sulla modifica sopra descritte, le #temptabelle possono anche essere associate a compilazioni aggiuntive semplicemente perché consentono operazioni vietate per le variabili di tabella che attivano una compilazione (ad esempio modifiche DDL CREATE INDEX, ALTER TABLE)

Blocco

È stato affermato che le variabili di tabella non partecipano al blocco. Questo non è il caso. Eseguendo gli output seguenti nella scheda Messaggi SSMS, i dettagli dei blocchi acquisiti e rilasciati per un'istruzione insert.

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)

Per le query che SELECTdalle variabili della tabella Paul White sottolinea nei commenti che queste vengono automaticamente fornite con un NOLOCKsuggerimento implicito . Questo è mostrato sotto

DECLARE @T TABLE(X INT); 

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

Produzione

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

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

L'impatto di questo sul blocco potrebbe tuttavia essere di lieve entità.

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

Nessuno di questi risultati restituisce un ordine di chiave indice che indica che SQL Server ha utilizzato una scansione ordinata per allocazione per entrambi.

Ho eseguito lo script sopra due volte e i risultati per la seconda esecuzione sono riportati di seguito

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 

L'output del blocco per la variabile della tabella è davvero estremamente minimo poiché SQL Server acquisisce solo un blocco della stabilità dello schema sull'oggetto. Ma per un #temptavolo è quasi altrettanto leggero in quanto elimina un Sblocco a livello di oggetto . Un NOLOCKsuggerimento o READ UNCOMMITTEDlivello di isolamento può naturalmente essere specificato in modo esplicito quando si lavora con #temple tabelle pure.

Analogamente al problema con la registrazione di una transazione dell'utente circostante, è possibile che i blocchi vengano mantenuti più a lungo per le #temptabelle. Con lo script qui sotto

    --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 eseguito al di fuori di una transazione utente esplicita per entrambi i casi, l'unico blocco restituito durante il controllo sys.dm_tran_locksè un blocco condiviso su DATABASE.

Quando si rimuove il commento, le BEGIN TRAN ... ROLLBACK26 righe vengono restituite mostrando che i blocchi vengono mantenuti sia sull'oggetto stesso che sulle righe della tabella di sistema per consentire il rollback e impedire ad altre transazioni di leggere dati non impegnati. L'operazione di variabile tabella equivalente non è soggetta a rollback con la transazione dell'utente e non è necessario conservare questi blocchi per consentirci di verificare nell'istruzione successiva, ma i blocchi di traccia acquisiti e rilasciati in Profiler o utilizzando il flag di traccia 1200 mostrano che molti eventi di blocco continuano a si verificano.

indici

Per le versioni precedenti a SQL Server 2014 gli indici possono essere creati in modo implicito solo sulle variabili di tabella come effetto collaterale dell'aggiunta di un vincolo univoco o chiave primaria. Ciò significa ovviamente che sono supportati solo indici univoci. Un indice non cluster non univoco su una tabella con un indice cluster univoco può essere simulato tuttavia semplicemente dichiarandolo UNIQUE NONCLUSTEREDe aggiungendo la chiave CI alla fine della chiave NCI desiderata (SQL Server lo farebbe comunque dietro le quinte anche se un non univoco NCI potrebbe essere specificato)

Come dimostrato in precedenza, index_optionè possibile specificare vari s nella dichiarazione del vincolo tra cui DATA_COMPRESSION, IGNORE_DUP_KEYe FILLFACTOR(anche se non ha senso impostarne uno in quanto farebbe solo una differenza sulla ricostruzione dell'indice e non è possibile ricostruire gli indici sulle variabili della tabella!)

Inoltre, le variabili di tabella non supportano INCLUDEcolonne d, indici filtrati (fino al 2016) o partizionamento, mentre le #temptabelle (lo schema di partizione deve essere creato in tempdb).

Indici in SQL Server 2014

Gli indici non univoci possono essere dichiarati in linea nella definizione della variabile di tabella in SQL Server 2014. Di seguito è riportata una sintassi di esempio.

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*/
);

Indici in SQL Server 2016

Da CTP 3.1 è ora possibile dichiarare indici filtrati per variabili di tabella. Con RTM può accadere che anche le colonne incluse siano consentite, anche se probabilmente non lo trasformeranno in SQL16 a causa di vincoli di risorse

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

Parallelismo

Le query che vengono inserite (o modificate in altro modo) @table_variablesnon possono avere un piano parallelo, #temp_tablesnon sono limitate in questo modo.

C'è una soluzione apparente in quella riscrittura, poiché la SELECTparte seguente permette che la parte si svolga in parallelo ma che finisce usando una tabella temporanea nascosta (dietro le quinte)

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

Non vi è tale limitazione nelle query che selezionano dalle variabili di tabella come illustrato nella mia risposta qui

Altre differenze funzionali

  • #temp_tablesnon può essere utilizzato all'interno di una funzione. @table_variablespuò essere utilizzato all'interno di UDF scalari o multiistruzione.
  • @table_variables non può avere vincoli con nome.
  • @table_variablesnon può essere SELECT-ed INTO, ALTER-ed, TRUNCATEd o essere il bersaglio di DBCCcomandi come DBCC CHECKIDENTo di SET IDENTITY INSERTe non supporta suggerimenti di tabella comeWITH (FORCESCAN)
  • CHECK i vincoli sulle variabili di tabella non vengono considerati dall'ottimizzatore per semplificazione, predicati impliciti o rilevamento di contraddizioni.
  • Le variabili di tabella non sembrano qualificarsi per l' ottimizzazione della condivisione del set di righe, il che significa che eliminare e aggiornare i piani rispetto a questi può incontrare più sovraccarico e PAGELATCH_EXattese. ( Esempio )

Solo memoria?

Come affermato all'inizio, entrambi vengono archiviati nelle pagine in tempdb. Tuttavia, non ho indicato se ci fossero differenze nel comportamento quando si tratta di scrivere queste pagine su disco.

Ho fatto una piccola quantità di test su questo ora e finora non ho visto alcuna differenza. Nel test specifico che ho fatto sulla mia istanza di SQL Server 250 pagine sembra essere il punto di interruzione prima che il file di dati venga scritto.

NB: il comportamento riportato di seguito non si verifica più in SQL Server 2014 o SQL Server 2012 SP1 / CU10 o SP2 / CU1 il writer desideroso non è più desideroso di scrivere pagine su disco. Maggiori dettagli su tale modifica in SQL Server 2014: tempdb Hidden Performance Gem .

Esecuzione dello script seguente

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 il monitoraggio scrive sul tempdbfile di dati con Process Monitor non ne ho visto nessuno (tranne quelli occasionali nella pagina di avvio del database con offset 73.728). Dopo essere passato 250a 251ho iniziato a vedere le scritture come di seguito.

ProcMon

Lo screenshot sopra mostra le scritture di pagine 5 * 32 e una singola pagina che indica che 161 delle pagine sono state scritte su disco. Ho avuto lo stesso punto di interruzione di 250 pagine durante il test anche con le variabili di tabella. Lo script seguente mostra un modo diverso di guardaresys.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 

risultati

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

Mostra che 192 pagine sono state scritte su disco e la bandiera sporca cancellata. Mostra anche che la scrittura su disco non significa che le pagine verranno immediatamente eliminate dal pool di buffer. Le query su questa variabile di tabella potrebbero essere soddisfatte interamente dalla memoria.

Su un server inattivo con pagine di pool di buffer max server memoryimpostate 2000 MBe di DBCC MEMORYSTATUSreport allocate come circa 1.843.000 KB (circa 23.000 pagine) ho inserito le tabelle sopra in lotti di 1.000 righe / pagine e per ogni iterazione registrata.

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

Sia la variabile della #temptabella che la tabella hanno dato grafici quasi identici e sono riusciti a massimizzare il pool di buffer prima di arrivare al punto in cui non erano interamente conservati in memoria, quindi non sembra esserci alcuna limitazione particolare su quanta memoria entrambi possono consumare.

Pagine nel pool di buffer


Ho scoperto che SQL Server acquisisce significativamente più blocchi durante la creazione di tabelle temporanee (anche con memorizzazione nella cache) rispetto a una variabile di tabella equivalente. Puoi provare usando il debug latch_acquired XE e creando una tabella con circa 35 colonne o giù di lì. Ho trovato la variabile della tabella per prendere 4 chiusure e la tabella temporanea per prendere da qualche parte circa 70 chiusure.
Joe Obbish,

40

Ci sono alcune cose che vorrei sottolineare sulla base di esperienze particolari piuttosto che di studio. Come DBA, sono molto nuovo, quindi per favore correggimi dove richiesto.

  1. Le tabelle #temp per impostazione predefinita utilizzano le regole di confronto predefinite dell'istanza di SQL Server. Quindi, se non diversamente specificato, potresti riscontrare problemi nel confrontare o aggiornare i valori tra le tabelle #temp e le tabelle del database, se il masterdb ha regole di confronto diverse dal database. Vedi: http://www.mssqltips.com/sqlservertip/2440/create-sql-server-temporary-tables-with-the-correct-collation/
  2. Completamente basato sull'esperienza personale, la memoria disponibile sembra avere un effetto su cui funziona meglio. MSDN consiglia di utilizzare variabili di tabella per l'archiviazione di set di risultati più piccoli, ma il più delle volte la differenza non è nemmeno evidente. In set più grandi, tuttavia, è ovvio in alcuni casi che le variabili di tabella richiedono molta più memoria e possono rallentare la query fino a una scansione.

6
Si noti inoltre che le regole di confronto nelle tabelle #temp possono ereditare le regole di confronto del database chiamante se si utilizza SQL Server 2012 e il database è contenuto.
Aaron Bertrand

Chiarimento sul n. 2 per piccoli e grandi set stackoverflow.com/a/14465163/5224021
GibralTop
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.