Je regardais l'article ici Tables temporaires par rapport aux variables de table et leur impact sur les performances de SQL Server et sur SQL Server 2008 a été en mesure de reproduire des résultats similaires à ceux indiqués ici pour 2005.
Lors de l'exécution des procédures stockées (définitions ci-dessous) avec seulement 10 lignes, la version de variable de table exécute la version de table temporaire plus de deux fois.
J'ai effacé le cache de procédure et exécuté les deux procédures stockées 10 000 fois, puis répété le processus pendant 4 autres exécutions. Résultats ci-dessous (temps en ms par lot)
T2_Time V2_Time
----------- -----------
8578 2718
6641 2781
6469 2813
6766 2797
6156 2719
Ma question est la suivante: quelle est la raison de la meilleure performance de la version de la variable de table?
J'ai fait des recherches. Par exemple, regarder les compteurs de performance avec
SELECT cntr_value
from sys.dm_os_performance_counters
where counter_name = 'Temp Tables Creation Rate';
confirme que, dans les deux cas, les objets temporaires sont mis en cache après la première exécution, comme prévu, plutôt que créés à nouveau pour chaque invocation.
De même , le suivi Auto Stats
, SP:Recompile
, les SQL:StmtRecompile
événements dans Profiler (capture d' écran ci - dessous) montre que ces événements se produisent qu'une seule fois (la première invocation de la #temp
table de procédure stockée) et les autres 9.999 exécutions ne soulèvent aucun de ces événements. (La version de la variable de table ne reçoit aucun de ces événements)
La surcharge légèrement plus importante de la première exécution de la procédure stockée ne peut en aucune manière expliquer la grande différence globale, toutefois, comme il ne faut que quelques ms pour effacer le cache de procédure et exécuter les deux procédures une fois, je ne crois pas que les statistiques ou Les recompilations peuvent en être la cause.
Créer les objets de base de données requis
CREATE DATABASE TESTDB_18Feb2012;
GO
USE TESTDB_18Feb2012;
CREATE TABLE NUM
(
n INT PRIMARY KEY,
s VARCHAR(128)
);
WITH NUMS(N)
AS (SELECT TOP 1000000 ROW_NUMBER() OVER (ORDER BY $/0)
FROM master..spt_values v1,
master..spt_values v2)
INSERT INTO NUM
SELECT N,
'Value: ' + CONVERT(VARCHAR, N)
FROM NUMS
GO
CREATE PROCEDURE [dbo].[T2] @total INT
AS
CREATE TABLE #T
(
n INT PRIMARY KEY,
s VARCHAR(128)
)
INSERT INTO #T
SELECT n,
s
FROM NUM
WHERE n%100 > 0
AND n <= @total
DECLARE @res VARCHAR(128)
SELECT @res = MAX(s)
FROM NUM
WHERE n <= @total
AND NOT EXISTS(SELECT *
FROM #T
WHERE #T.n = NUM.n)
GO
CREATE PROCEDURE [dbo].[V2] @total INT
AS
DECLARE @V TABLE (
n INT PRIMARY KEY,
s VARCHAR(128))
INSERT INTO @V
SELECT n,
s
FROM NUM
WHERE n%100 > 0
AND n <= @total
DECLARE @res VARCHAR(128)
SELECT @res = MAX(s)
FROM NUM
WHERE n <= @total
AND NOT EXISTS(SELECT *
FROM @V V
WHERE V.n = NUM.n)
GO
Script de test
SET NOCOUNT ON;
DECLARE @T1 DATETIME2,
@T2 DATETIME2,
@T3 DATETIME2,
@Counter INT = 0
SET @T1 = SYSDATETIME()
WHILE ( @Counter < 10000)
BEGIN
EXEC dbo.T2 10
SET @Counter += 1
END
SET @T2 = SYSDATETIME()
SET @Counter = 0
WHILE ( @Counter < 10000)
BEGIN
EXEC dbo.V2 10
SET @Counter += 1
END
SET @T3 = SYSDATETIME()
SELECT DATEDIFF(MILLISECOND,@T1,@T2) AS T2_Time,
DATEDIFF(MILLISECOND,@T2,@T3) AS V2_Time
la source
#temp
table qu'une fois, bien qu'elles aient été effacées et à nouveau remplies 9 999 fois par la suite.Réponses:
La sortie de
SET STATISTICS IO ON
pour les deux ressembleDonne
Et comme Aaron le souligne dans les commentaires, le plan de la version de la variable de table est en réalité moins efficace, car les deux ont un plan de boucles imbriquées piloté par un index. La recherche de
dbo.NUM
la#temp
version de la table effectue une recherche dans l'index[#T].n = [dbo].[NUM].[n]
avec le prédicat résiduel,[#T].[n]<=[@total]
tandis que version effectue une recherche d'index@V.n <= [@total]
avec le prédicat résiduel@V.[n]=[dbo].[NUM].[n]
et traite ainsi plus de lignes (c'est pourquoi ce plan fonctionne si mal pour un plus grand nombre de lignes)L'utilisation d' événements étendus pour examiner les types d'attente du spid spécifique donne ces résultats pour 10 000 exécutions de
EXEC dbo.T2 10
et ces résultats pour 10 000 exécutions de
EXEC dbo.V2 10
Il est donc clair que le nombre d’
PAGELATCH_SH
attentes est beaucoup plus élevé dans le#temp
cas de la table. Je ne suis au courant d'aucun moyen d'ajouter la ressource d'attente à la trace des événements étendus. Pour approfondir cette question, j'ai lancéDans une autre connexion, interrogation
sys.dm_os_waiting_tasks
Après avoir fonctionné pendant environ 15 secondes, il avait obtenu les résultats suivants
Ces deux pages verrouillées appartiennent à (différents) index non clusterisés sur la
tempdb.sys.sysschobjs
table de base nommée'nc1'
and'nc2'
.L'interrogation au
tempdb.sys.fn_dblog
cours des exécutions indique que le nombre d'enregistrements de journal ajoutés lors de la première exécution de chaque procédure stockée était quelque peu variable, mais que pour les exécutions suivantes, le nombre ajouté à chaque itération était très cohérent et prévisible. Une fois que les plans de procédure sont mis en cache, le nombre d'entrées de journal est environ la moitié de celui requis pour la#temp
version.En examinant plus en détail les entrées du journal des transactions pour la
#temp
version de table du SP, chaque appel ultérieur de la procédure stockée crée trois transactions et la variable de table un seulement deux.Les
INSERT
/TVQUERY
transactions sont identiques à l'exception du nom. Celui-ci contient les enregistrements de journal pour chacune des 10 lignes insérées dans la table temporaire ou la variable de table plus les entréesLOP_BEGIN_XACT
/LOP_COMMIT_XACT
.La
CREATE TABLE
transaction apparaît uniquement dans la#Temp
version et se présente comme suit.La
FCheckAndCleanupCachedTempTable
transaction apparaît dans les deux, mais comporte 6 entrées supplémentaires dans la#temp
version. Ce sont les 6 rangées en référencesys.sysschobjs
et ils ont exactement le même motif que ci-dessus.En regardant ces 6 lignes dans les deux transactions, elles correspondent aux mêmes opérations. Le premier
LOP_MODIFY_ROW, LCX_CLUSTERED
est une mise à jour de lamodify_date
colonne danssys.objects
. Les cinq lignes restantes concernent toutes le renommage d'objets. Parce quename
c’est une colonne clé des deux NCI concernés (nc1
etnc2
), cette opération est effectuée en tant que suppression / insertion pour ceux-ci, puis elle retourne à l’index clusterisé et est également mise à jour.Il semble que pour la
#temp
version de la table, lorsque la procédure stockée se termine, une partie du nettoyage effectué par laFCheckAndCleanupCachedTempTable
transaction consiste à renommer la table temporaire#T__________________________________________________________________________________________________________________00000000E316
en un nom similaire à celui d'un nom interne différent, par exemple#2F4A0079
lorsque laCREATE TABLE
transaction est entrée, la renomme. Ce nom est visible dans une connexion s’exécutantdbo.T2
dans une boucle alors que dans une autreExemple de résultats
Ainsi, une explication potentielle du différentiel de performance observé évoquée par Alex est que c'est ce travail supplémentaire de maintenance des tables système
tempdb
qui est responsable.L'exécution des deux procédures en boucle par le profileur de code Visual Studio révèle les éléments suivants:
La version de la variable de table consacre environ 60% du temps à l'exécution de l'instruction d'insertion et à la sélection ultérieure, tandis que la table temporaire correspond à moins de la moitié de celle-ci. Cela correspond aux délais indiqués dans le PO et à la conclusion ci-dessus, à savoir que la différence de performances est due au temps consacré à l'exécution de travaux auxiliaires et non au temps consacré à l'exécution de la requête elle-même.
Les fonctions les plus importantes contribuant aux 75% "manquants" dans la version à table temporaire sont
Sous les fonctions create et release, la fonction
CMEDProxyObject::SetName
est affichée avec une valeur d'échantillon inclusif de19.6%
. J'en déduis que 39,2% du temps de la table temporaire est utilisé avec le changement de nom décrit plus haut.Et les plus gros dans la version de la variable de table contribuant à l'autre 40% sont
Profil de table temporaire
Table Variable Profile
la source
Disco Inferno
Comme il s’agit d’une question plus ancienne, j’ai décidé de revenir sur la question des versions plus récentes de SQL Server pour voir si le même profil de performances existait toujours ou si les caractéristiques avaient changé.
Plus précisément, l'ajout de tables système en mémoire pour SQL Server 2019 semble être une occasion intéressante de procéder à un nouveau test.
J'utilise un harnais de test légèrement différent, car j'ai rencontré ce problème alors que je travaillais sur autre chose.
Test, test
En utilisant la version 2013 de Stack Overflow , j'ai cet index et ces deux procédures:
Indice:
Table de temp:
Variable de table:
Pour éviter toute attente potentielle ASYNC_NETWORK_IO , j'utilise des procédures d'encapsulation.
SQL Server 2017
Depuis 2014 et 2016 sont essentiellement des RELICS à ce stade, je commence mes tests avec 2017. En outre, par souci de concision, je vais tout de suite passer au profilage du code avec Perfview . Dans la vie réelle, j'ai examiné les attentes, les verrous, les verrous pivotants, les indicateurs de traces fous, etc.
Le profilage du code est la seule chose qui ait révélé quelque chose d'intéressant.
Différence de temps:
Encore une différence très nette, hein? Mais qu'est-ce que SQL Server frappe maintenant?
En regardant les deux plus fortes augmentations d'échantillons différenciés, nous voyons
sqlmin
etsqlsqllang!TCacheStore<CacheClockAlgorithm>::GetNextUserDataInHashBucket
sommes les deux plus gros délinquants.À en juger par les noms dans les piles d’appel, le nettoyage et le renommage interne des tables temporaires semblent être le temps le plus long dans l’appel de la table temporaire par rapport à celui de la variable table.
Même si les variables de table sont sauvegardées en interne par des tables temporaires, cela ne semble pas poser de problème.
En regardant à travers les piles d'appels pour le test de variable de table, on ne voit aucun des principaux délinquants:
SQL Server 2019 (Vanilla)
Bon, c'est donc toujours un problème dans SQL Server 2017, y a-t-il autre chose de différent en 2019?
Tout d'abord, pour montrer qu'il n'y a rien dans ma manche:
Différence de temps:
Les deux procédures étaient différentes. L'appel de table temporaire a été quelques secondes plus rapide et l'appel de variable de table a été environ 1,5 seconde plus lent. La lenteur de la variable de table peut s’expliquer en partie par la compilation différée de la variable de table , un nouveau choix de l’optimiseur en 2019.
En regardant le diff dans Perfview, cela a un peu changé - sqlmin n’est plus là - mais le
sqllang!TCacheStore<CacheClockAlgorithm>::GetNextUserDataInHashBucket
reste.SQL Server 2019 (tables système Tempdb en mémoire)
Qu'en est-il de cette nouvelle chose dans la table système en mémoire? Hm? Sup avec ça?
Allumons-le!
Notez que cela nécessite un redémarrage de SQL Server, alors excusez-moi pendant le redémarrage de SQL en ce beau vendredi après-midi.
Maintenant, les choses sont différentes:
Différence de temps:
Les tables temporaires ont fait environ 4 secondes de mieux! C'est quelque chose.
J'aime quelque chose
Cette fois, le diff Perfview n’est pas très intéressant. Côte à côte, il est intéressant de noter à quel point les temps sont proches l'un de l'autre:
Les appels à
hkengine!
, qui peuvent sembler évidents depuis que les fonctionnalités hekaton-ish sont maintenant utilisées, constituent un point intéressant du diff .En ce qui concerne les deux premiers articles du diff, je ne peux pas en dire beaucoup
ntoskrnl!?
:Ou
sqltses!CSqlSortManager_80::GetSortKey
, mais ils sont là pour que Smrtr Ppl ™ regarde:Notez qu’il existe un document non documenté et qu’il n’est absolument pas sûr pour la production. Par conséquent, n’utilisez pas cet indicateur de trace de démarrage que vous pouvez utiliser pour inclure des objets système de table temporaire supplémentaires (sysrowsets, sysallocunits et sysseobjvalues) inclus dans la fonction en mémoire. n'a pas fait une différence notable dans les temps d'exécution dans ce cas.
Roundup
Même dans les versions les plus récentes de SQL Server, les appels haute fréquence aux variables de table sont beaucoup plus rapides que les appels haute fréquence aux tables temporaires.
Bien qu'il soit tentant de blâmer les compilations, les recompilations, les statistiques automatiques, les verrous, les verrous spinblocks, la mise en cache ou d'autres problèmes, le problème reste clairement lié à la gestion du nettoyage de la table temporaire.
C'est un appel plus étroit dans SQL Server 2019 avec les tables système en mémoire activées, mais les variables de table fonctionnent toujours mieux lorsque la fréquence des appels est élevée.
Bien sûr, comme un sage de vapotage avait une fois déclaré: "utilisez des variables de tableau lorsque le choix du plan n'est pas un problème".
la source