Table temporaire vs table variable

Dans cette article nous allons voir les différences entre les tables temporaires marquées par dièse (#Temp) et le variables de type table (@Temp).

Emplacement mémoire / disque

Les tables temporaires allouent l'espace dans la base de données (BDD) temporaire (tempdb) ainsi que variables peut utiliser la BDD temporaire ainsi que la mémoire vive.

Normalement, la variable commence allouer l’espace de disque depuis qu'elle dépasse la taille d'un extente qui égal à 8 pages (soit 64 Ko).

SET NOCOUNT ON
USE tempdb
DECLARE @V TABLE (Name nchar(4000)) -- une page environ pour une ligne
DECLARE @i int = 0
WHILE @i < 100 BEGIN
   IF @i % 10 = 0 OR @i BETWEEN 1 AND 9 OR @i = 99
      SELECT 
         @i AS lines_in_table_variable, 
         unallocated_extent_page_count, 
         user_object_reserved_page_count
      FROM sys.dm_db_file_space_usage
   INSERT INTO @V (Name)
      SELECT convert(nvarchar(4), @i) + replicate(N'V', 3996)
   SET @i = @i + 1
END

Résultats :

lines_in_table_variable unallocated_extent_page_count user_object_reserved_page_count
----------------------- ----------------------------- -------------------------------
0                       4144                          40
1                       4144                          40
2                       4144                          40
3                       4144                          40
4                       4144                          40
5                       4144                          40
6                       4144                          40
7                       4144                          40
8                       4144                          40
9                       4136                          48
^^^
10                      4136                          48
20                      4128                          56
30                      4120                          64
40                      4112                          72
50                      4096                          88
60                      4088                          96
70                      4080                          104
80                      4072                          112
90                      4056                          128
99                      4048                          136

Métadonnées

Lorsque vous déclarez une variable ou crée une tables temporaire, les informations sur les métadonnées sont bien placées dans les tables systèmes.

USE tempdb
DECLARE @V TABLE (Id int, Name nvarchar(100))
CREATE TABLE #T (Id int, Name nvarchar(100))
SELECT OBJECT_NAME(object_id) AS object_name, object_id, name 
FROM sys.columns WHERE object_id > 127 AND name IN ('Id', 'Name')
DROP TABLE #T

Résultats :

object_name              object_id   name
------------------------ ----------- -----
#0D7A0286                226099846   Id
#T__________00000000002D 242099903   Id
#0D7A0286                226099846   Name
#T__________00000000002D 242099903   Name

Transactions

Les tables temporaires sont inclus dans la transaction explicite ainsi que les variables sont dehors de celle-ci.

SET NOCOUNT ON
CREATE TABLE #T (Id int)
DECLARE @V TABLE (Id int)
 
INSERT INTO #T VALUES(1)
INSERT INTO @V VALUES(1)
 
BEGIN TRANSACTION
INSERT INTO #T VALUES(2)
INSERT INTO @V VALUES(2)
ROLLBACK
 
SELECT Id AS [Id #T] FROM #T
SELECT Id AS [Id @V] FROM @V

Résultats :

Id #T
-----------
1
 
Id @V
-----------
1
2

Indexation

Vous pouvez créer les index sur la table temporaires et non pas sur la variable; Par contre, vous pouvez déclarer une clé primaire ou une contrainte d'unicité lorsque vous déclarez la variable.

CREATE TABLE #T (Id int)
DECLARE @V TABLE (Id int)
 
CREATE INDEX IX1_TT ON #T(Id)
CREATE INDEX IX2_TT ON @V(Id)  -- !! Msg 102, Level 15, State 1, Line 5
                               -- !! Incorrect syntax near '@V'
GO
 
DECLARE @V TABLE (Id int PRIMARY KEY, Name nvarchar(100) UNIQUE)
GO

Visibilité

La variable n'est visible qu'en code d'une procédure stockée, mais elle peut être passée comme le paramètre READONLY (la déclaration d'un type utilisateurs est necessaire).

La table temporaire n’existe que pendent le temps d’exécution de la procédure stockée mais elle est visible pour tous les procédures appelées par celle qui crée la table.

CREATE TYPE MyTableType AS TABLE ( Id int)
GO
 
CREATE PROCEDURE P1
AS
BEGIN
   SET NOCOUNT ON
   DECLARE @V MyTableType
   CREATE TABLE #T (Id int)
 
   INSERT INTO @V (Id) VALUES (1)
   INSERT INTO #T (Id) VALUES (2)
 
   EXEC P2
   EXEC P3 @V
END
GO
 
CREATE PROCEDURE P2
AS
BEGIN
   SET NOCOUNT ON
   SELECT Id AS [Id #T] FROM #T
END
GO
 
CREATE PROCEDURE P3
   @V MyTableType READONLY
AS
BEGIN
   SET NOCOUNT ON
   SELECT Id AS [Id @V] FROM @V
END
GO
 
EXEC P1
GO
 
DROP PROCEDURE P1
DROP PROCEDURE P2
DROP PROCEDURE P3
GO
DROP TYPE MyTableType
GO

Résultats :

Id #T
-----------
2
 
Id @V
-----------
1

Egalement, la table temporaire peut être déclarée comme "globale" avec deux dièses (##Temp) et ensuit être visible pour toutes les connexions.

Questions de performance

Modifications de données

Comme les variable sont dehors de la transaction, les opérations d'insertion, de modifications ou de suppression des données seront un peu plus rapides.

CREATE PROCEDURE Test_TableTmp
   @InsertCount int
AS
BEGIN
   DECLARE @Start datetime = getdate();
   CREATE TABLE #T (Id int PRIMARY KEY, StrValue nvarchar(100))
   DECLARE @i int = 1
   WHILE @i <= @InsertCount BEGIN
      INSERT INTO #T (Id, StrValue)
         VALUES(@i, convert(nvarchar(100), rand() * @i))
      SET @i = @i + 1
   END
   SELECT @InsertCount AS LinesInserted, datediff(ms, @Start, getdate()) AS [ElapsedTime, msec]
END
GO
 
CREATE PROCEDURE Test_TableVar
   @InsertCount int
AS
BEGIN
   DECLARE @Start datetime = getdate();
   DECLARE @V TABLE (Id int PRIMARY KEY, StrValue nvarchar(100))
   DECLARE @i int = 1
   WHILE @i <= @InsertCount BEGIN
      INSERT INTO @V (Id, StrValue)
         VALUES(@i, convert(nvarchar(100), rand() * @i))
      SET @i = @i + 1
   END
   SELECT @InsertCount AS LinesInserted, datediff(ms, @Start, getdate()) AS [ElapsedTime, msec]
END
GO
 
DBCC DROPCLEANBUFFERS
GO
EXEC Test_TableTmp 100
EXEC Test_TableVar 100
EXEC Test_TableTmp 1000
EXEC Test_TableVar 1000
EXEC Test_TableTmp 10000
EXEC Test_TableVar 10000
EXEC Test_TableTmp 100000
EXEC Test_TableVar 100000
GO
DROP PROCEDURE Test_TableTmp
DROP PROCEDURE Test_TableVar
GO

Résultats :

Lignes insérées

Temps, msec

Table temporaire Variable
100 33 3
1 000 26 23
10 000 240 226
100 000 2426 2290

Sélection de données

Sachant que vous ne pouvez pas créer les index sur les variables, les opérations de sélection de données peuvent être beaucoup plus lentes de que le nombre de lignes s'augmente.

En plus, en cas d'une variable l’optimiseur des requêtes SQL Server n'est pas capable créer le plan pour le traitement parallèle ainsi que c'est possible pour les tables temporaires.

Ré-compilation

Les versions anciennes de SQL Server ont pu ré-compiler les procédures stockées qui utilisent les tables temporaires chaque fois on l'appelle. Ce problème n'est plus important depuis 2005 dans la majorité de cas.

USE AdventureWorks
GO
SET NOCOUNT ON
GO
CREATE PROCEDURE dbo.TestP1
   @LastName nvarchar(100),
   @Divider int
AS
BEGIN
   SET NOCOUNT ON
   CREATE TABLE #T (Id int, Name nvarchar(100), NameLen int)
 
   INSERT INTO #T (Id, Name, NameLen)
   SELECT C.ContactID, C.LastName, 0
      FROM Person.Contact C
      WHERE C.LastName LIKE @LastName
   UPDATE #T
   SET NameLen = len(Name)
   WHERE Id % @Divider <> 0
 
   EXEC dbo.TestP2
END
GO
 
CREATE PROCEDURE dbo.TestP2
AS
BEGIN
   SELECT COUNT(*) AS NbDeLignes FROM #T WHERE NameLen > 0
END
GO
 
SELECT object_name(objid) AS procedure_name, usecounts 
FROM sys.syscacheobjects WHERE objid IN (object_id('dbo.TestP1'), object_id('dbo.TestP2'))
 
EXEC dbo.TestP1 'Ada%', 2
SELECT object_name(objid) AS procedure_name, usecounts 
FROM sys.syscacheobjects WHERE objid IN (object_id('dbo.TestP1'), object_id('dbo.TestP2'))
 
EXEC dbo.TestP1 'Smi%', 4
SELECT object_name(objid) AS procedure_name, usecounts 
FROM sys.syscacheobjects WHERE objid IN (object_id('dbo.TestP1'), object_id('dbo.TestP2'))
GO
 
DROP PROCEDURE dbo.TestP1
DROP PROCEDURE dbo.TestP2
GO

Résultats :

procedure_name  usecounts
--------------- -----------
 
NbDeLignes
-----------
43
 
procedure_name  usecounts
--------------- -----------
TestP1          1
TestP2          1
 
NbDeLignes
-----------
80
 
procedure_name  usecounts
--------------- -----------
TestP1          2
TestP2          2

Conclusions

La variable de type table est une bonne solution pour le traitement des petites volumes (< 1000 lignes environ ou < 1 extent) de données dont la sélection par les critères n'est pas nécessaire. Le passage de paramètre de type table par variable est plus contrôlé et lisible.

Dans les autres cas il vaut mieux utiliser les tables temporaires.