416 votes

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

Dans SQL Server 2005, nous pouvons créer des tables temporaires de deux façons :

declare @tmp table (Col1 int, Col2 int);

o

create table #tmp (Col1 int, Col2 int);

Quelles sont les différences entre les deux ? J'ai lu des avis contradictoires sur la question de savoir si @tmp utilise toujours tempdb, ou si tout se passe en mémoire.

Dans quels scénarios l'un est-il plus performant que l'autre ?

15 votes

4 votes

Il y a un très bon article de Pinal Dave ici... blog.sqlauthority.com/2009/12/15/

414voto

Rory Points 13087

Il existe quelques différences entre les tables temporaires (#tmp) et les variables de table (@tmp), bien que l'utilisation de tempdb n'en fasse pas partie, comme l'explique le lien MSDN ci-dessous.

En règle générale, pour des volumes de données faibles à moyens et des scénarios d'utilisation simples, vous devriez utiliser des variables de table. (Il s'agit d'une directive excessivement large avec bien sûr de nombreuses exceptions - voir ci-dessous et les articles suivants).

Quelques points à prendre en compte pour choisir entre les deux :

  • Les tables temporaires sont de vraies tables, ce qui vous permet de faire des choses comme créer des index, etc. Si vous avez de grandes quantités de données pour lesquelles l'accès par index sera plus rapide, les tables temporaires sont une bonne option.

  • Les variables de table peuvent avoir des index en utilisant les contraintes PRIMARY KEY ou UNIQUE. (Si vous voulez un index non unique, il suffit d'inclure la colonne de la clé primaire comme dernière colonne de la contrainte unique. Si vous ne disposez pas d'une colonne unique, vous pouvez utiliser une colonne d'identité). SQL 2014 a aussi des index non uniques .

  • Les variables de table ne participent pas aux transactions et SELECT sont implicitement avec NOLOCK . Le comportement de la transaction peut être très utile, par exemple si vous voulez faire un ROLLBACK au milieu d'une procédure, les variables de la table qui ont été remplies pendant cette transaction seront toujours remplies !

  • Les tables temporaires peuvent entraîner la recompilation des procédures stockées, peut-être souvent. Les variables de table ne le seront pas.

  • Vous pouvez créer une table temporaire en utilisant SELECT INTO, ce qui peut être plus rapide à écrire (bon pour les requêtes ad-hoc) et peut vous permettre de faire face à des changements de types de données au fil du temps, puisque vous n'avez pas besoin de définir la structure de votre table temporaire au départ.

  • Vous pouvez renvoyer des variables de tableau à partir de fonctions, ce qui vous permet d'encapsuler et de réutiliser la logique beaucoup plus facilement (par exemple, créer une fonction pour diviser une chaîne de caractères en un tableau de valeurs sur un délimiteur arbitraire).

  • L'utilisation des variables de table dans les fonctions définies par l'utilisateur permet d'élargir l'utilisation de ces fonctions (voir la documentation CREATE FUNCTION pour plus de détails). Si vous écrivez une fonction, vous devriez utiliser des variables de table plutôt que des tables temporaires, à moins qu'il n'y ait un besoin impératif de procéder autrement.

  • Les variables de table et les tables temporaires sont toutes deux stockées dans tempdb. Mais les variables de table (depuis 2005) utilisent par défaut la collation de la base de données actuelle, alors que les tables temporaires utilisent la collation par défaut de tempdb ( réf. ). Cela signifie que vous devez être conscient des problèmes de collation si vous utilisez des tables temporaires et que la collation de votre base de données est différente de celle de tempdb, ce qui pose des problèmes si vous voulez comparer les données de la table temporaire avec celles de votre base de données.

  • Les tables temporaires globales (##tmp) sont un autre type de table temporaire disponible pour toutes les sessions et tous les utilisateurs.

Quelques lectures complémentaires :

28 votes

Les variables de table peuvent avoir des index. Il suffit de créer une contrainte unique, et vous obtenez automatiquement un index. Cela fait une énorme différence en termes de performances. (Si vous ne voulez pas d'index unique, ajoutez simplement la clé primaire réelle à la fin des champs que vous voulez. Si vous n'en avez pas, créez une colonne d'identité).

7 votes

@Ben Et SQL Server 2014 permet les index non uniques à spécifier sur les variables de la table

5 votes

Les variables des tables ne sont pas affectées par les transactions, ce qui est parfois pratique. Si vous avez quelque chose que vous voulez conserver après un retour en arrière, vous pouvez le mettre dans une variable de table.

26voto

Martin Smith Points 174101

Il suffit de regarder l'affirmation dans la réponse acceptée que les variables de table ne participent pas à la journalisation.

Il semble généralement faux de dire qu'il y a une différence dans la quantité d'exploitation forestière (du moins pour l'industrie du bois). insert / update / delete à la table elle-même, bien que j'aie depuis trouvé qu'il existe une petite différence à cet égard pour les objets temporaires mis en cache dans les procédures stockées, en raison des mises à jour supplémentaires des tables système).

J'ai examiné le comportement d'enregistrement par rapport à une @table_variable et un #temp pour les opérations suivantes.

  1. Insertion réussie
  2. Insertion de plusieurs lignes où l'instruction est annulée en raison de la violation d'une contrainte.
  3. Mise à jour
  4. Supprimer
  5. Désallocation de

Les enregistrements du journal des transactions étaient presque identiques pour toutes les opérations.

La version variable de la table a en fait quelques extra parce qu'une entrée est ajoutée (puis supprimée) à la base de données du journal. sys.syssingleobjrefs mais dans l'ensemble, quelques octets de moins ont été enregistrés, car le nom interne des variables de la table consomme 236 octets de moins que celui de la table de base. #temp tables (118 en moins) nvarchar caractères).

script complet à reproduire (à exécuter de préférence sur une instance démarrée en mode mono-utilisateur et utilisant sqlcmd mode)

:setvar tablename "@T" 
:setvar tablescript "DECLARE @T TABLE"

/*
 --Uncomment this section to test a #temp table
:setvar tablename "#T" 
:setvar tablescript "CREATE TABLE #T"
*/

USE tempdb 
GO    
CHECKPOINT

DECLARE @LSN NVARCHAR(25)

SELECT @LSN = MAX([Current LSN])
FROM fn_dblog(null, null) 

EXEC(N'BEGIN TRAN StartBatch
SAVE TRAN StartBatch
COMMIT

$(tablescript)
(
[4CA996AC-C7E1-48B5-B48A-E721E7A435F0] INT PRIMARY KEY DEFAULT 0,
InRowFiller char(7000) DEFAULT ''A'',
OffRowFiller varchar(8000) DEFAULT REPLICATE(''B'',8000),
LOBFiller varchar(max) DEFAULT REPLICATE(cast(''C'' as varchar(max)),10000)
)

BEGIN TRAN InsertFirstRow
SAVE TRAN InsertFirstRow
COMMIT

INSERT INTO $(tablename)
DEFAULT VALUES

BEGIN TRAN Insert9Rows
SAVE TRAN Insert9Rows
COMMIT

INSERT INTO $(tablename) ([4CA996AC-C7E1-48B5-B48A-E721E7A435F0])
SELECT TOP 9 ROW_NUMBER() OVER (ORDER BY (SELECT 0))
FROM sys.all_columns

BEGIN TRAN InsertFailure
SAVE TRAN InsertFailure
COMMIT

/*Try and Insert 10 rows, the 10th one will cause a constraint violation*/
BEGIN TRY
INSERT INTO $(tablename) ([4CA996AC-C7E1-48B5-B48A-E721E7A435F0])
SELECT TOP (10) (10 + ROW_NUMBER() OVER (ORDER BY (SELECT 0))) % 20
FROM sys.all_columns
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE()
END CATCH

BEGIN TRAN Update10Rows
SAVE TRAN Update10Rows
COMMIT

UPDATE $(tablename)
SET InRowFiller = LOWER(InRowFiller),
    OffRowFiller  =LOWER(OffRowFiller),
    LOBFiller  =LOWER(LOBFiller)

BEGIN TRAN Delete10Rows
SAVE TRAN Delete10Rows
COMMIT

DELETE FROM  $(tablename)
BEGIN TRAN AfterDelete
SAVE TRAN AfterDelete
COMMIT

BEGIN TRAN EndBatch
SAVE TRAN EndBatch
COMMIT')

DECLARE @LSN_HEX NVARCHAR(25) = 
        CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 1, 8),2) AS INT) AS VARCHAR) + ':' +
        CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 10, 8),2) AS INT) AS VARCHAR) + ':' +
        CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 19, 4),2) AS INT) AS VARCHAR)        

SELECT 
    [Operation],
    [Context],
    [AllocUnitName],
    [Transaction Name],
    [Description]
FROM   fn_dblog(@LSN_HEX, null) AS D
WHERE  [Current LSN] > @LSN  

SELECT CASE
         WHEN GROUPING(Operation) = 1 THEN 'Total'
         ELSE Operation
       END AS Operation,
       Context,
       AllocUnitName,
       COALESCE(SUM([Log Record Length]), 0) AS [Size in Bytes],
       COUNT(*)                              AS Cnt
FROM   fn_dblog(@LSN_HEX, null) AS D
WHERE  [Current LSN] > @LSN  
GROUP BY GROUPING SETS((Operation, Context, AllocUnitName),())

Résultats

+-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+
|                       |                    |                           |             @TV      |             #TV      |                  |
+-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+
| Operation             | Context            | AllocUnitName             | Size in Bytes | Cnt  | Size in Bytes | Cnt  | Difference Bytes |
+-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+
| LOP_ABORT_XACT        | LCX_NULL           |                           | 52            | 1    | 52            | 1    |                  |
| LOP_BEGIN_XACT        | LCX_NULL           |                           | 6056          | 50   | 6056          | 50   |                  |
| LOP_COMMIT_XACT       | LCX_NULL           |                           | 2548          | 49   | 2548          | 49   |                  |
| LOP_COUNT_DELTA       | LCX_CLUSTERED      | sys.sysallocunits.clust   | 624           | 3    | 624           | 3    |                  |
| LOP_COUNT_DELTA       | LCX_CLUSTERED      | sys.sysrowsets.clust      | 208           | 1    | 208           | 1    |                  |
| LOP_COUNT_DELTA       | LCX_CLUSTERED      | sys.sysrscols.clst        | 832           | 4    | 832           | 4    |                  |
| LOP_CREATE_ALLOCCHAIN | LCX_NULL           |                           | 120           | 3    | 120           | 3    |                  |
| LOP_DELETE_ROWS       | LCX_INDEX_INTERIOR | Unknown Alloc Unit        | 720           | 9    | 720           | 9    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysallocunits.clust   | 444           | 3    | 444           | 3    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysallocunits.nc      | 276           | 3    | 276           | 3    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.syscolpars.clst       | 628           | 4    | 628           | 4    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.syscolpars.nc         | 484           | 4    | 484           | 4    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysidxstats.clst      | 176           | 1    | 176           | 1    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysidxstats.nc        | 144           | 1    | 144           | 1    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysiscols.clst        | 100           | 1    | 100           | 1    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysiscols.nc1         | 88            | 1    | 88            | 1    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysobjvalues.clst     | 596           | 5    | 596           | 5    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysrowsets.clust      | 132           | 1    | 132           | 1    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysrscols.clst        | 528           | 4    | 528           | 4    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysschobjs.clst       | 1040          | 6    | 1276          | 6    | 236              |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysschobjs.nc1        | 820           | 6    | 1060          | 6    | 240              |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysschobjs.nc2        | 820           | 6    | 1060          | 6    | 240              |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.sysschobjs.nc3        | 480           | 6    | 480           | 6    |                  |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.syssingleobjrefs.clst | 96            | 1    |               |      | -96              |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | sys.syssingleobjrefs.nc1  | 88            | 1    |               |      | -88              |
| LOP_DELETE_ROWS       | LCX_MARK_AS_GHOST  | Unknown Alloc Unit        | 72092         | 19   | 72092         | 19   |                  |
| LOP_DELETE_ROWS       | LCX_TEXT_MIX       | Unknown Alloc Unit        | 16348         | 37   | 16348         | 37   |                  |
| LOP_FORMAT_PAGE       | LCX_HEAP           | Unknown Alloc Unit        | 1596          | 19   | 1596          | 19   |                  |
| LOP_FORMAT_PAGE       | LCX_IAM            | Unknown Alloc Unit        | 252           | 3    | 252           | 3    |                  |
| LOP_FORMAT_PAGE       | LCX_INDEX_INTERIOR | Unknown Alloc Unit        | 84            | 1    | 84            | 1    |                  |
| LOP_FORMAT_PAGE       | LCX_TEXT_MIX       | Unknown Alloc Unit        | 4788          | 57   | 4788          | 57   |                  |
| LOP_HOBT_DDL          | LCX_NULL           |                           | 108           | 3    | 108           | 3    |                  |
| LOP_HOBT_DELTA        | LCX_NULL           |                           | 9600          | 150  | 9600          | 150  |                  |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.sysallocunits.clust   | 456           | 3    | 456           | 3    |                  |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.syscolpars.clst       | 644           | 4    | 644           | 4    |                  |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.sysidxstats.clst      | 180           | 1    | 180           | 1    |                  |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.sysiscols.clst        | 104           | 1    | 104           | 1    |                  |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.sysobjvalues.clst     | 616           | 5    | 616           | 5    |                  |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.sysrowsets.clust      | 136           | 1    | 136           | 1    |                  |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.sysrscols.clst        | 544           | 4    | 544           | 4    |                  |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.sysschobjs.clst       | 1064          | 6    | 1300          | 6    | 236              |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | sys.syssingleobjrefs.clst | 100           | 1    |               |      | -100             |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | Unknown Alloc Unit        | 135888        | 19   | 135888        | 19   |                  |
| LOP_INSERT_ROWS       | LCX_INDEX_INTERIOR | Unknown Alloc Unit        | 1596          | 19   | 1596          | 19   |                  |
| LOP_INSERT_ROWS       | LCX_INDEX_LEAF     | sys.sysallocunits.nc      | 288           | 3    | 288           | 3    |                  |
| LOP_INSERT_ROWS       | LCX_INDEX_LEAF     | sys.syscolpars.nc         | 500           | 4    | 500           | 4    |                  |
| LOP_INSERT_ROWS       | LCX_INDEX_LEAF     | sys.sysidxstats.nc        | 148           | 1    | 148           | 1    |                  |
| LOP_INSERT_ROWS       | LCX_INDEX_LEAF     | sys.sysiscols.nc1         | 92            | 1    | 92            | 1    |                  |
| LOP_INSERT_ROWS       | LCX_INDEX_LEAF     | sys.sysschobjs.nc1        | 844           | 6    | 1084          | 6    | 240              |
| LOP_INSERT_ROWS       | LCX_INDEX_LEAF     | sys.sysschobjs.nc2        | 844           | 6    | 1084          | 6    | 240              |
| LOP_INSERT_ROWS       | LCX_INDEX_LEAF     | sys.sysschobjs.nc3        | 504           | 6    | 504           | 6    |                  |
| LOP_INSERT_ROWS       | LCX_INDEX_LEAF     | sys.syssingleobjrefs.nc1  | 92            | 1    |               |      | -92              |
| LOP_INSERT_ROWS       | LCX_TEXT_MIX       | Unknown Alloc Unit        | 5112          | 71   | 5112          | 71   |                  |
| LOP_MARK_SAVEPOINT    | LCX_NULL           |                           | 508           | 8    | 508           | 8    |                  |
| LOP_MODIFY_COLUMNS    | LCX_CLUSTERED      | Unknown Alloc Unit        | 1560          | 10   | 1560          | 10   |                  |
| LOP_MODIFY_HEADER     | LCX_HEAP           | Unknown Alloc Unit        | 3780          | 45   | 3780          | 45   |                  |
| LOP_MODIFY_ROW        | LCX_CLUSTERED      | sys.syscolpars.clst       | 384           | 4    | 384           | 4    |                  |
| LOP_MODIFY_ROW        | LCX_CLUSTERED      | sys.sysidxstats.clst      | 100           | 1    | 100           | 1    |                  |
| LOP_MODIFY_ROW        | LCX_CLUSTERED      | sys.sysrowsets.clust      | 92            | 1    | 92            | 1    |                  |
| LOP_MODIFY_ROW        | LCX_CLUSTERED      | sys.sysschobjs.clst       | 1144          | 13   | 1144          | 13   |                  |
| LOP_MODIFY_ROW        | LCX_IAM            | Unknown Alloc Unit        | 4224          | 48   | 4224          | 48   |                  |
| LOP_MODIFY_ROW        | LCX_PFS            | Unknown Alloc Unit        | 13632         | 169  | 13632         | 169  |                  |
| LOP_MODIFY_ROW        | LCX_TEXT_MIX       | Unknown Alloc Unit        | 108640        | 120  | 108640        | 120  |                  |
| LOP_ROOT_CHANGE       | LCX_CLUSTERED      | sys.sysallocunits.clust   | 960           | 10   | 960           | 10   |                  |
| LOP_SET_BITS          | LCX_GAM            | Unknown Alloc Unit        | 1200          | 20   | 1200          | 20   |                  |
| LOP_SET_BITS          | LCX_IAM            | Unknown Alloc Unit        | 1080          | 18   | 1080          | 18   |                  |
| LOP_SET_BITS          | LCX_SGAM           | Unknown Alloc Unit        | 120           | 2    | 120           | 2    |                  |
| LOP_SHRINK_NOOP       | LCX_NULL           |                           |               |      | 32            | 1    | 32               |
+-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+
| Total                 |                    |                           | 410144        | 1095 | 411232        | 1092 | 1088             |
+-----------------------+--------------------+---------------------------+---------------+------+---------------+------+------------------+

1 votes

+1 Juste par curiosité (et pour être un peu pédant). La question est/était assez ancienne (août 2008), elle concernait donc SQL 2005. Nous sommes maintenant en 2011 (fin 2011) et la dernière version de SQL est 2008 R2 plus la version bêta de Denali. Quelle version avez-vous utilisée ?

2 votes

@xanatos - 2008. Sur la table 2005 les variables seraient en fait désavantagées car INSERT ... SELECT n'a pas été enregistré de façon minimale et vous ne pouvez pas SELECT INTO ... une variable de table.

1 votes

Merci @MartinSmith, j'ai mis à jour ma réponse pour supprimer l'affirmation concernant la journalisation.

19voto

SQLMenace Points 68670

Dans quels scénarios l'un est-il plus performant que l'autre ?

Pour les petites tables (moins de 1000 lignes), utilisez une variable temporaire, sinon utilisez une table temporaire.

17 votes

Des données à l'appui ? Ce n'est pas très utile en tant qu'affirmation isolée.

8 votes

Microsoft recommande une limite de 100 lignes : msdn.microsoft.com/fr/us/library/ms175010.aspx (voir la section Meilleures pratiques).

0 votes

Voir mon réponse ci-dessous pour une explication.

17voto

JamesSugrue Points 8426

@wcm - En fait, pour pinailler, la variable de table n'est pas uniquement binaire - elle peut être partiellement stockée sur le disque.

Une table temporaire peut avoir des index, alors qu'une variable de table ne peut avoir qu'un index primaire. Si la vitesse est un problème, les variables de table peuvent être plus rapides, mais il est évident que s'il y a beaucoup d'enregistrements, ou s'il est nécessaire d'effectuer une recherche dans la table temporaire d'un index en grappe, alors une table temporaire est préférable.

Bon article de fond

2 votes

Bon article de fond +1. Je vais supprimer ma réponse car la modifier n'apporterait pas grand chose et il y a déjà tellement de bonnes réponses.

9voto

SQLMenace Points 68670

Pour tous ceux qui croient au mythe selon lequel les variables temporelles ne sont qu'en mémoire

Premièrement, la variable de la table n'est PAS nécessairement résidente en mémoire. Sous pression mémoire, les pages appartenant à une variable de table peuvent être poussées vers tempdb.

Lisez l'article ici : TempDB : : Table variable vs table temporaire locale

3 votes

Pouvez-vous éditer vos réponses en une seule réponse traitant des deux points ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X