web-dev-qa-db-fra.com

Quelle est la différence entre une table temporaire et une variable de table dans SQL Server?

Cela semble être un domaine avec pas mal de mythes et d'opinions contradictoires.

Quelle est donc la différence entre une variable de table et une table temporaire locale dans SQL Server?

459
Martin Smith

Contenu

Contents

Avertissement

Cette réponse traite des variables de table "classiques" introduites dans SQL Server 2000. SQL Server 2014 en mémoire OLTP présente les types de table optimisés en mémoire. Les instances de variables de table de celles-ci sont différentes à bien des égards de celles décrites ci-dessous! ( plus de détails ).

Emplacement de stockage

Aucune différence. Les deux sont stockés dans tempdb.

Je l'ai vu suggérer que pour les variables de table, ce n'est pas toujours le cas, mais cela peut être vérifié à partir de ce qui suit

DECLARE @T TABLE(X INT)

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

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

Exemple de résultats (montrant l'emplacement dans tempdb les 2 lignes sont stockées)

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

Emplacement logique

@table_variables Se comportent plus comme s'ils faisaient partie de la base de données actuelle que les tables #temp. Pour les variables de table (depuis 2005), les classements des colonnes, s'ils ne sont pas spécifiés explicitement, seront ceux de la base de données actuelle, tandis que pour les tables #temp, Il utilisera le classement par défaut de tempdb ( Plus de détails ). De plus, les types de données définis par l'utilisateur et les collections XML doivent être dans tempdb à utiliser pour les tables #temp, Mais les variables de table peuvent les utiliser à partir de la base de données actuelle ( Source ).

SQL Server 2012 présente les bases de données contenues. le comportement des tables temporaires diffère (h/t Aaron)

Dans une base de données contenue, les données d'une table temporaire sont rassemblées dans le classement de la base de données contenue.

  • Toutes les métadonnées associées aux tables temporaires (par exemple, les noms de table et de colonne, les index, etc.) seront dans le classement du catalogue.
  • Les contraintes nommées ne peuvent pas être utilisées dans les tables temporaires.
  • Les tables temporaires peuvent ne pas faire référence à des types définis par l'utilisateur, à des collections de schémas XML ou à des fonctions définies par l'utilisateur.

Visibilité sur différentes portées

@table_variables N'est accessible que dans le lot et la portée dans lesquels ils sont déclarés. #temp_tables Sont accessibles dans des lots enfants (déclencheurs imbriqués, procédure, exec appels). #temp_tables Créé sur la portée externe (@@NESTLEVEL=0) Peut également s'étendre sur des lots car ils persistent jusqu'à la fin de la session. Aucun des deux types d'objet ne peut être créé dans un lot enfant et accessible dans la portée d'appel, comme expliqué ci-après (tables globales ##temp can cependant).

Durée de vie

@table_variables Sont créés implicitement lorsqu'un lot contenant une instruction DECLARE @.. TABLE Est exécuté (avant l'exécution de tout code utilisateur de ce lot) et sont supprimés implicitement à la fin.

Bien que l'analyseur ne vous permette pas d'essayer et d'utiliser la variable de table avant l'instruction DECLARE, la création implicite peut être vue ci-dessous.

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

--Works fine
SELECT *
FROM @T

#temp_tables Sont créés explicitement lorsque l'instruction TSQL CREATE TABLE Est rencontrée et peuvent être supprimés explicitement avec DROP TABLE Ou seront supprimés implicitement à la fin du lot (s'ils sont créés dans un lot enfant avec @@NESTLEVEL > 0) Ou à la fin de la session sinon.

NB: Dans les routines stockées, les deux types d'objet peuvent être mis en cache plutôt que de créer et de supprimer plusieurs fois de nouvelles tables. Il existe des restrictions sur le moment où cette mise en cache peut se produire, mais il est possible de violer pour #temp_tables Mais que les restrictions sur @table_variables Empêchent de toute façon. La surcharge de maintenance pour les tables #temp Mises en cache est légèrement supérieure à celle des variables de table comme illustré ici .

Métadonnées d'objet

C'est essentiellement la même chose pour les deux types d'objet. Il est stocké dans les tables de base du système dans tempdb. Cependant, il est plus simple de voir une table #temp Car OBJECT_ID('tempdb..#T') peut être utilisé pour saisir les tables système et le nom généré en interne est plus étroitement corrélé avec le nom défini dans le CREATE TABLE. Pour les variables de table, la fonction object_id Ne fonctionne pas et le nom interne est entièrement généré par le système sans relation avec le nom de la variable. Ce qui suit montre que les métadonnées sont toujours là cependant en entrant un nom de colonne (espérons-le unique). Pour les tables sans nom de colonne unique, le object_id peut être déterminé en utilisant DBCC PAGE tant qu'ils ne sont pas vides.

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

Production

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

Transactions

Les opérations sur @table_variables Sont effectuées en tant que transactions système, indépendamment de toute transaction utilisateur externe, tandis que les opérations de table #temp Équivalentes sont effectuées dans le cadre de la transaction utilisateur elle-même. Pour cette raison, une commande ROLLBACK affectera une table #temp Mais laissera la touche @table_variable Intacte.

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

Journalisation

Les deux génèrent des enregistrements de journal dans le journal de transactions tempdb. Une idée fausse commune est que ce n'est pas le cas pour les variables de table, donc un script démontrant cela est ci-dessous, il déclare une variable de table, ajoute quelques lignes, puis les met à jour et les supprime.

Étant donné que la variable de table est créée et supprimée implicitement au début et à la fin du lot, il est nécessaire d'utiliser plusieurs lots afin de voir la journalisation complète.

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 

Retour

Vue détaillée

Screenshot of results

Vue récapitulative (inclut la journalisation des tables de base implicites de dépôt et système)

Screenshot of results

Autant que j'ai pu discerner les opérations sur les deux génèrent des quantités de journalisation à peu près égales.

Alors que la quantité de consignation est très similaire, une différence importante est que les enregistrements de journaux liés aux tables #temp Ne peuvent pas être effacés jusqu'à ce qu'une transaction utilisateur contenant se termine donc une transaction de longue durée qui à un moment donné, l'écriture dans les tables #temp empêchera la troncature du journal dans tempdb, contrairement aux transactions autonomes générées pour les variables de table.

Les variables de table ne prennent pas en charge TRUNCATE et peuvent donc être désavantagées lors de la journalisation lorsque l'exigence est de supprimer toutes les lignes d'une table (bien que pour les très petites tables DELETEcela peut mieux fonctionner quand même) )

Cardinalité

De nombreux plans d'exécution impliquant des variables de table afficheront une seule ligne estimée comme la sortie d'eux. L'inspection des propriétés de la variable de table montre que SQL Server pense que la variable de table a zéro lignes (pourquoi elle estime qu'une ligne sera émise à partir d'une table de lignes zéro est expliquée par @Paul White ici ).

Cependant, les résultats montrés dans la section précédente montrent un compte rows précis dans sys.partitions. Le problème est que, dans la plupart des cas, les instructions faisant référence aux variables de table sont compilées alors que la table est vide. Si l'instruction est (re) compilée après le remplissage de @table_variable, Elle sera utilisée à la place pour la cardinalité de la table (cela peut se produire en raison d'un recompile explicite ou peut-être parce que l'instruction fait également référence à un autre objet qui provoque une compilation différée ou une recompilation.)

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

Le plan affiche un nombre précis de lignes estimé après la compilation différée.

Shows accurate row count

Dans SQL Server 2012 SP2, l'indicateur de trace 2453 est introduit. Plus de détails sont sous "Moteur relationnel" ici .

Lorsque cet indicateur de trace est activé, il peut entraîner des recompilations automatiques pour tenir compte de la modification de la cardinalité, comme nous le verrons très prochainement.

NB: Sur Azure au niveau de compatibilité 150, la compilation de l'instruction est désormais différée jusqu'à la première exécution . Cela signifie qu'il ne sera plus soumis au problème d'estimation de ligne zéro.

Aucune statistique de colonne

Avoir une cardinalité de table plus précise ne signifie cependant pas que le nombre de lignes estimé sera plus précis (à moins d'effectuer une opération sur toutes les lignes de la table). SQL Server ne gère pas du tout les statistiques de colonne pour les variables de table, il se rabattra donc sur des suppositions basées sur le prédicat de comparaison (par exemple, 10% de la table sera retourné pour un = Contre une colonne non unique ou 30% pour une comparaison >). En revanche, les statistiques des colonnes sont conservées pour les tables #temp.

SQL Server conserve un décompte du nombre de modifications apportées à chaque colonne. Si le nombre de modifications depuis la compilation du plan dépasse le seuil de recompilation (RT), le plan sera recompilé et les statistiques mises à jour. Le RT dépend du type et de la taille du tableau.

De Planification de la mise en cache dans SQL Server 2008

RT est calculé comme suit. (n fait référence à la cardinalité d'une table lors de la compilation d'un plan de requête.)

Table permanente
- Si n <= 500, RT = 500.
- Si n> 500, RT = 500 + 0,20 * n.

Table temporaire
- Si n <6, RT = 6.
- Si 6 <= n <= 500, RT = 500.
- Si n> 500, RT = 500 + 0,20 * n.
Variable de table
- RT n'existe pas. Par conséquent, les recompilations ne se produisent pas en raison des changements de cardinalités des variables de table. (Mais voir la note sur TF 2453 ci-dessous)

le conseil KEEP PLAN peut être utilisé pour définir les RT pour les tables #temp de la même manière que pour les tables permanentes.

L'effet net de tout cela est que souvent les plans d'exécution générés pour les tables #temp Sont des ordres de grandeur meilleurs que pour @table_variables Lorsque de nombreuses lignes sont impliquées car SQL Server dispose de meilleures informations pour travailler.

NB1: les variables de table n'ont pas de statistiques mais peuvent toujours subir un événement de recompilation "Statistiques modifiées" sous l'indicateur de trace 2453 (ne s'applique pas aux plans "triviaux"). Cela semble se produire sous les mêmes seuils de recompilation comme indiqué pour les tables temporaires ci-dessus avec un supplémentaire que si N=0 -> RT = 1. c'est-à-dire que toutes les instructions compilées lorsque la variable de table est vide finiront par être recompilées et corrigées TableCardinality la première fois qu'elles seront exécutées lorsqu'elles ne sont pas vides. La cardinalité du tableau de compilation est stockée dans le plan et si l'instruction est exécutée à nouveau avec la même cardinalité (soit en raison du flux d'instructions de contrôle ou de la réutilisation d'un plan mis en cache), aucune recompilation ne se produit.

NB2: Pour les tables temporaires mises en cache dans les procédures stockées, l'histoire de recompilation est beaucoup plus compliquée que celle décrite ci-dessus. Voir Tables temporaires dans les procédures stockées pour tous les détails sanglants.

Recompile

En plus des recompilations basées sur les modifications décrites ci-dessus, les tables #temp Peuvent également être associées à compilations supplémentaires simplement parce qu'elles autorisent des opérations interdites pour les variables de table qui déclenchent une compilation (par exemple, les modifications DDL CREATE INDEX, ALTER TABLE)

Verrouillage

Il a été déclaré que les variables de table ne participent pas au verrouillage. Ce n'est pas le cas. Exécuter les sorties ci-dessous dans l'onglet des messages SSMS les détails des verrous pris et libérés pour une instruction d'insertion.

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)

Pour les requêtes qui SELECT à partir des variables de table, Paul White souligne dans les commentaires qu'elles sont automatiquement accompagnées d'un indice implicite NOLOCK. Ceci est illustré ci-dessous

DECLARE @T TABLE(X INT); 

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

Production

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

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

L'impact de ceci sur le verrouillage pourrait cependant être assez mineur.

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

Aucun de ces résultats ne renvoie dans l'ordre des clés d'index indiquant que SQL Server a utilisé un analyse ordonnée d'allocation pour les deux.

J'ai exécuté le script ci-dessus deux fois et les résultats de la deuxième exécution sont ci-dessous

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 sortie de verrouillage pour la variable de table est en effet extrêmement minime car SQL Server vient d'acquérir un verrou de stabilité de schéma sur l'objet. Mais pour une table #temp, Elle est presque aussi légère en ce qu'elle supprime un verrou d'objet S. Un niveau d'isolation NOLOCK hint ou READ UNCOMMITTED Peut bien entendu être spécifié explicitement lorsque vous travaillez avec des tables #temp Également.

De la même manière que pour la journalisation d'une transaction utilisateur environnante, les verrous sont maintenus plus longtemps pour les tables #temp. Avec le script ci-dessous

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

lorsqu'il est exécuté en dehors d'une transaction utilisateur explicite dans les deux cas, le seul verrou renvoyé lors de la vérification de sys.dm_tran_locks est un verrou partagé sur le DATABASE.

Lors de la décommentation, BEGIN TRAN ... ROLLBACK 26 lignes sont renvoyées, indiquant que les verrous sont maintenus à la fois sur l'objet lui-même et sur les lignes de la table système pour permettre la restauration et empêcher d'autres transactions de lire des données non validées. L'opération de variable de table équivalente n'est pas sujette à une restauration avec la transaction utilisateur et n'a pas besoin de maintenir ces verrous pour que nous puissions vérifier dans l'instruction suivante, mais le traçage des verrous acquis et libérés dans Profiler ou à l'aide de l'indicateur de trace 1200 montre que de nombreux événements de verrouillage se produisent toujours se produire.

Index

Pour les versions antérieures à SQL Server 2014, les index peuvent uniquement être créés implicitement sur des variables de table comme effet secondaire de l'ajout d'une contrainte unique ou d'une clé primaire. Cela signifie bien sûr que seuls les index uniques sont pris en charge. Un index non cluster non unique sur une table avec un index cluster unique peut être simulé cependant en le déclarant simplement UNIQUE NONCLUSTERED Et en ajoutant la clé CI à la fin de la clé NCI souhaitée (SQL Server do cela dans les coulisses de toute façon même si un NCI non unique pourrait être spécifié)

Comme démontré précédemment, divers index_option Peuvent être spécifiés dans la déclaration de contrainte, y compris DATA_COMPRESSION, IGNORE_DUP_KEY Et FILLFACTOR (bien qu'il ne soit pas utile de définir celui-là car cela ne ferait aucune différence lors de la reconstruction d'index et vous ne pouvez pas reconstruire d'index sur des variables de table!)

De plus, les variables de table ne prennent pas en charge les colonnes INCLUDEd, les index filtrés (jusqu'en 2016) ou le partitionnement, les tables #temp Le font (le schéma de partition doit être créé dans tempdb).

Index dans SQL Server 2014

Les index non uniques peuvent être déclarés en ligne dans la définition de variable de table dans SQL Server 2014. Un exemple de syntaxe pour cela est ci-dessous.

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

Index dans SQL Server 2016

Depuis CTP 3.1, il est désormais possible de déclarer des index filtrés pour les variables de table. By RTM it may le cas où les colonnes incluses sont également autorisées bien qu'elles ne seront probablement pas intégrées à SQL16 en raison de contraintes de ressources =

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

Parallélisme

Les requêtes qui s'insèrent dans (ou modifient d'une autre manière) @table_variables Ne peuvent pas avoir de plan parallèle, #temp_tables Ne sont pas limitées de cette manière.

Il existe une solution de contournement apparente dans la mesure où la réécriture comme suit permet à la partie SELECT de se dérouler en parallèle, mais qui finit par utiliser une table temporaire masquée (dans les coulisses)

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

Il n'y a pas une telle limitation dans les requêtes qui sélectionnent parmi les variables du tableau comme illustré dans ma réponse ici

Autres différences fonctionnelles

  • #temp_tables Ne peut pas être utilisé dans une fonction. @table_variables Peut être utilisé dans des UDF de table scalaire ou multi-instructions.
  • @table_variables Ne peut pas avoir de contraintes nommées.
  • @table_variables Ne peut pas être SELECT- ed INTO, ALTER- ed, TRUNCATEd ou être la cible des commandes DBCC tels que DBCC CHECKIDENT ou de SET IDENTITY INSERT et ne prennent pas en charge les indications de tableau telles que WITH (FORCESCAN)
  • CHECK les contraintes sur les variables de table ne sont pas prises en compte par l'optimiseur pour la simplification, les prédicats implicites ou la détection des contradictions.
  • Les variables de table ne semblent pas être admissibles à optimisation du partage de l'ensemble de lignes ce qui signifie que les plans de suppression et de mise à jour par rapport à ceux-ci peuvent rencontrer plus de surcharge et PAGELATCH_EX Attend. ( exemple )

Mémoire uniquement?

Comme indiqué au début, les deux sont stockés sur des pages dans tempdb. Cependant, je n'ai pas cherché à savoir s'il y avait une différence de comportement en ce qui concerne l'écriture de ces pages sur disque.

J'ai fait un petit nombre de tests à ce sujet maintenant et jusqu'à présent, je n'ai vu aucune différence. Dans le test spécifique que j'ai fait sur mon instance de SQL Server, 250 pages semblent être le point de coupure avant l'écriture du fichier de données.

NB: Le comportement ci-dessous ne se produit plus dans SQL Server 2014 ou SQL Server 2012 SP1/CU10 ou SP2/CU1 l'écrivain impatient n'est plus aussi désireux d'écrire des pages sur le disque. Plus de détails sur cette modification sur SQL Server 2014: tempdb Hidden Performance Gem .

Exécution du script ci-dessous

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

Et la surveillance écrit dans le fichier de données tempdb avec Process Monitor, je n'en ai vu aucun (sauf occasionnellement sur la page de démarrage de la base de données au décalage 73 728). Après avoir changé 250 En 251, J'ai commencé à voir les écritures comme ci-dessous.

ProcMon

La capture d'écran ci-dessus montre des écritures de 5 * 32 pages et une écriture d'une seule page indiquant que 161 des pages ont été écrites sur le disque. J'ai également obtenu le même point de coupure de 250 pages lors des tests avec les variables de table. Le script ci-dessous le montre d'une manière différente en regardant sys.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 

Résultats

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

Montrant que 192 pages ont été écrites sur le disque et le drapeau sale effacé. Cela montre également que l'écriture sur disque ne signifie pas que les pages seront immédiatement expulsées du pool de tampons. Les requêtes sur cette variable de table peuvent toujours être entièrement satisfaites à partir de la mémoire.

Sur un serveur inactif avec max server memory Réglé sur 2000 MB Et DBCC MEMORYSTATUS Rapporter les pages du pool de mémoire tampon allouées à environ 1 843 000 Ko (c. 23 000 pages) J'ai inséré les tableaux ci-dessus par lots de 1 000 lignes/pages et pour chaque itération enregistrée.

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

La variable de table et la table #temp Ont donné des graphiques presque identiques et ont réussi à maximiser le pool de tampons avant d'arriver au point qu'elles n'étaient pas entièrement conservées en mémoire, donc il ne semble pas y en avoir limitation particulière de la quantité de mémoire pouvant être consommée.

Pages in Buffer Pool

682
Martin Smith

Il y a quelques choses que j'aimerais souligner basées davantage sur des expériences particulières plutôt que sur des études. En tant que DBA, je suis très nouveau, alors corrigez-moi si nécessaire.

  1. Les tables #temp utilisent par défaut le classement par défaut de l'instance SQL Server. Donc, sauf indication contraire, vous pouvez rencontrer des problèmes pour comparer ou mettre à jour les valeurs entre les tables #temp et les tables de base de données, si masterdb a un classement différent de la base de données. Voir: http://www.mssqltips.com/sqlservertip/2440/create-sql-server-temporary-tables-with-the-correct-collation/
  2. Entièrement basée sur l'expérience personnelle, la mémoire disponible semble avoir un effet sur celui qui fonctionne mieux. MSDN recommande d'utiliser des variables de table pour stocker des jeux de résultats plus petits, mais la plupart du temps, la différence n'est même pas perceptible. Cependant, dans les ensembles plus volumineux, il est devenu évident dans certains cas que les variables de table consomment beaucoup plus de mémoire et peuvent ralentir la requête jusqu'à une analyse.
41
Kahn