Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Mise en cache d'objets temporaires SQL Server

La création d'une table est une opération relativement gourmande en ressources et chronophage. Le serveur doit localiser et allouer de l'espace de stockage pour les nouvelles structures de données et d'index, et créer les entrées correspondantes dans plusieurs tables de métadonnées système. Tout ce travail doit être effectué d'une manière qui fonctionnera toujours correctement dans des conditions de concurrence élevée et qui répondra à toutes les garanties ACID attendues d'une base de données relationnelle.

Dans SQL Server, cela signifie prendre les bons types de verrous et de verrous, dans le bon ordre, tout en s'assurant que les entrées détaillées du journal des transactions sont enregistrées en toute sécurité dans le stockage persistant avant toute modification physique de la base de données. Ces entrées de journal garantissent que le système peut ramener la base de données à un état cohérent en cas d'annulation d'une transaction ou de plantage du système.

La suppression d'une table est une opération tout aussi coûteuse. Heureusement, la plupart des bases de données ne créent pas ou ne suppriment pas de tables très fréquemment. L'exception évidente à cela est la base de données système tempdb . Cette base de données unique contient le stockage physique, les structures d'allocation, les métadonnées système et les entrées du journal des transactions pour toutes les tables temporaires et variables de table sur l'ensemble de l'instance SQL Server.

Il est dans la nature des tables temporaires et des variables de table d'être créées et supprimées beaucoup plus fréquemment que les autres types d'objets de base de données. Lorsque cette fréquence naturellement élevée de création et de destruction est combinée à l'effet de concentration de toutes les tables temporaires et des variables de table associées à une seule base de données, il n'est guère surprenant que des conflits puissent survenir dans les structures d'allocation et de métadonnées de tempdb base de données.

Mise en cache d'objets temporaires

Pour réduire l'impact sur tempdb structures, SQL Server peut mettre en cache des objets temporaires pour les réutiliser. Au lieu de supprimer un objet temporaire, SQL Server conserve les métadonnées système et tronque les données de la table. Si la table fait 8 Mo ou moins, la troncature est effectuée de manière synchrone; sinon, la suppression différée est utilisée. Dans les deux cas, la troncature réduit les besoins de stockage à une seule page de données (vide) et les informations d'allocation à une seule page IAM.

La mise en cache évite presque tous les coûts d'allocation et de métadonnées liés à la création de l'objet temporaire la prochaine fois. En tant qu'effet secondaire d'apporter moins de modifications à la tempdb base de données qu'un cycle complet de suppression et de recréation, la mise en cache d'objets temporaires réduit également la quantité de journalisation des transactions requise.

Réaliser la mise en cache

Les variables de table et les tables temporaires locales peuvent toutes deux être mises en cache. Pour être éligible à la mise en cache, une table temporaire locale ou une variable de table doit être créé dans un module :

  • Procédure stockée (y compris une procédure stockée temporaire)
  • Déclencheur
  • Fonction table multi-instructions
  • Fonction scalaire définie par l'utilisateur

La valeur de retour d'une fonction table multi-instructions est une variable de table, qui peut elle-même être mise en cache. Les paramètres de table (qui sont également des variables de table) peuvent être mis en cache lorsque le paramètre est envoyé à partir d'une application cliente, par exemple dans le code .NET à l'aide de SqlDbType.Structured . Lorsque l'instruction est paramétrée, les structures de paramètres de table ne peuvent être mises en cache que sur SQL Server 2012 ou version ultérieure.

Ce qui suit ne peut pas être mis en cache :

  • Tables temporaires globales
  • Objets créés à l'aide de SQL ad hoc
  • Objets créés à l'aide de SQL dynamique (par exemple, en utilisant EXECUTE ou sys.sp_executesql )

Pour être mis en cache, un objet temporaire ne doit pas en outre :

  • Avoir des contraintes nommées (les contraintes sans nom explicite conviennent parfaitement)
  • Effectuer "DDL" après la création de l'objet
  • Être dans un module défini en utilisant le WITH RECOMPILE option
  • Être appelé en utilisant le WITH RECOMPILE option de EXECUTE déclaration

Pour répondre explicitement à certaines idées fausses courantes :

  • TRUNCATE TABLE ne pas empêcher la mise en cache
  • DROP TABLE ne pas empêcher la mise en cache
  • UPDATE STATISTICS ne pas empêcher la mise en cache
  • La création automatique de statistiques ne empêcher la mise en cache
  • Manuel CREATE STATISTICS va empêcher la mise en cache

Tous les objets temporaires d'un module sont évalués séparément pour leur adéquation à la mise en cache. Un module qui contient un ou plusieurs objets temporaires qui ne peuvent pas être mis en cache peut toujours être éligible pour la mise en cache d'autres objets temporaires dans le même module.

Un modèle courant qui désactive la mise en cache pour les tables temporaires est la création d'index après l'instruction de création de table initiale. Dans la plupart des cas, cela peut être contourné en utilisant une clé primaire et des contraintes uniques. Dans SQL Server 2014 et versions ultérieures, nous avons la possibilité d'ajouter des index non cluster non uniques directement dans l'instruction de création de table à l'aide de INDEX clause.

Surveillance et maintenance

Nous pouvons voir combien d'objets temporaires sont actuellement mis en cache en utilisant les compteurs de cache DMV :

SELECT
    DOMCC.[type],
    DOMCC.pages_kb,
    DOMCC.pages_in_use_kb,
    DOMCC.entries_count,
    DOMCC.entries_in_use_count
FROM sys.dm_os_memory_cache_counters AS DOMCC 
WHERE 
    DOMCC.[name] = N'Temporary Tables & Table Variables';

Un exemple de résultat est :

Une entrée de cache est considérée comme étant en cours d'utilisation aussi longtemps qu'une partie du module conteneur est en cours d'exécution. Les exécutions simultanées du même module entraîneront la création de plusieurs objets temporaires mis en cache. Plans d'exécution multiples pour le même module (peut-être en raison d'une session SET différente options) conduira également à plusieurs entrées de cache pour le même module.

Les entrées de cache peuvent vieillir au fil du temps en réponse à des besoins concurrents en mémoire. Les objets temporaires mis en cache peuvent également être supprimés (de manière asynchrone, par un thread système en arrière-plan) lorsque le plan d'exécution du module parent est supprimé du cache du plan.

Bien qu'il ne soit pas pris en charge (ou en aucun cas recommandé) pour les systèmes de production, le magasin de cache d'objets temporaire peut être complètement effacé manuellement à des fins de test avec :

DBCC FREESYSTEMCACHE('Temporary Tables & Table Variables')
    WITH MARK_IN_USE_FOR_REMOVAL;
WAITFOR DELAY '00:00:05';

Le délai de cinq secondes laisse le temps à la tâche de nettoyage en arrière-plan de s'exécuter. Notez que cette commande est en fait dangereuse . Vous ne devez l'utiliser (à vos risques et périls) que sur une instance de test à laquelle vous avez un accès exclusif. Une fois les tests terminés, redémarrez l'instance SQL Server.

Détails de mise en œuvre de la mise en cache

Les variables de table sont implémentées par une "vraie" table utilisateur dans tempdb base de données (mais pas une table que nous pouvons interroger directement). Le nom de la table associée est "#" suivi de la représentation hexadécimale à huit chiffres de l'identifiant de l'objet. La requête suivante montre la relation :

-- A table variable
DECLARE @Z AS table (z integer NULL);
 
-- Corresponding sys.tables entry
SELECT
    T.[name],
    ObjIDFromName = CONVERT(integer, CONVERT(binary(4), RIGHT(T.[name], 8), 2)),
    T.[object_id],
    T.[type_desc],
    T.create_date,
    T.modify_date
FROM tempdb.sys.tables AS T 
WHERE
    T.[name] LIKE N'#[0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F]';

Un exemple de résultat est présenté ci-dessous. Remarquez comment l'identifiant de l'objet calculé à partir du nom de l'objet correspond à l'identifiant réel de l'objet :

L'exécution de ce script en tant que SQL ad hoc produira un tempdb différent ID d'objet (et nom d'objet) à chaque exécution (pas de mise en cache). Placer le même script dans un module (par exemple, une procédure stockée) permettra de mettre en cache la variable de table (tant que le SQL dynamique n'est pas utilisé), de sorte que l'ID et le nom de l'objet seront les mêmes à chaque exécution.

Lorsque la variable de table n'est pas mise en cache, la table sous-jacente est créée et supprimée à chaque fois. Lorsque la mise en cache des objets temporaires est activée, la table est tronquée à la fin du module au lieu d'être supprimée. Il n'y a aucun changement aux métadonnées système lorsqu'une variable de table est mise en cache. L'impact sur les structures d'allocation et la journalisation des transactions se limite à la suppression des lignes du tableau et à la suppression de tout excès de données et de pages d'allocation à la fin du module.

Tableaux temporaires

Lorsqu'une table temporaire est utilisée à la place d'une variable de table, le mécanisme de base est essentiellement le même, avec seulement quelques étapes de renommage supplémentaires :lorsqu'une table temporaire n'est pas mise en cache , il est visible dans tempdb avec le nom familier fourni par l'utilisateur, suivi d'un tas de traits de soulignement et de la représentation hexadécimale de l'identifiant de l'objet comme suffixe final. La table temporaire locale reste jusqu'à ce qu'elle soit explicitement supprimée ou jusqu'à ce que la portée dans laquelle elle a été créée se termine. Pour SQL ad hoc, cela signifie lorsque la session se déconnecte du serveur.

Pour une table temporaire mise en cache , la première fois que le module est exécuté, la table temporaire est créée comme pour le cas non mis en cache. A la fin du module, au lieu d'être supprimée automatiquement (à la fin de la portée dans laquelle il a été créé), la table temporaire est tronquée puis renommée à la représentation hexadécimale de l'ID d'objet (exactement comme vu pour la variable de table). La prochaine fois que le module s'exécute, la table mise en cache est renommée du format hexadécimal au nom fourni par l'utilisateur (plus les traits de soulignement plus l'ID d'objet hexadécimal).

Les opérations de renommage supplémentaires au début et à la fin du module impliquent un petit nombre de changements de métadonnées du système . Les tables temporaires mises en cache peuvent donc encore rencontrer au moins quelques conflits de métadonnées avec des taux de réutilisation très élevés. Néanmoins, l'impact sur les métadonnées d'une table temporaire mise en cache est beaucoup plus faible que pour le cas non mis en cache (création et suppression de la table à chaque fois).

Vous trouverez plus de détails et d'exemples sur le fonctionnement de la mise en cache d'objets temporaires dans mon article précédent.

Statistiques sur les tables temporaires en cache

Comme mentionné précédemment, les statistiques peuvent être automatiquement créées sur des tables temporaires sans perdre les avantages de la mise en cache des objets temporaires (pour rappel, la création manuelle de statistiques permet désactiver la mise en cache).

Une mise en garde importante est que les statistiques associés à une table temporaire en cache ne sont pas réinitialisés lorsque l'objet est mis en cache à la fin du module, ou lorsque l'objet mis en cache est récupéré du cache au début du module. Par conséquent, les statistiques sur une table temporaire mise en cache peuvent être laissées par une exécution précédente non liée. En d'autres termes, les statistiques peuvent n'avoir absolument aucun rapport au contenu actuel de la table temporaire.

Ceci n'est évidemment pas souhaitable, étant donné que la principale raison de préférer une table temporaire locale à une variable de table est la disponibilité de statistiques de distribution précises. En guise d'atténuation, les statistiques seront automatiquement mises à jour lorsque (si) le nombre cumulé de modifications apportées à l'objet mis en cache sous-jacent atteint le seuil de recompilation interne. Ceci est difficile à évaluer à l'avance, car les détails sont complexes et quelque peu contre-intuitifs.

La solution de contournement la plus complète, tout en conservant les avantages de la mise en cache d'objets temporaires, consiste à :

  • Manuellement UPDATE STATISTICS sur la table temporaire du module ; et
  • Ajouter une OPTION (RECOMPILE) allusion aux déclarations qui font référence à la table temporaire

Naturellement, cela a un coût, mais cela est le plus souvent acceptable. En effet, en choisissant d'utiliser une table temporaire locale en premier lieu, l'auteur du module dit implicitement que la sélection du plan est susceptible d'être sensible au contenu de la table temporaire, donc la recompilation peut avoir du sens. La mise à jour manuelle des statistiques garantit que les statistiques utilisées lors de la recompilation reflètent le contenu actuel de la table (comme on pourrait s'y attendre).

Pour plus de détails sur la manière exacte dont cela fonctionne, veuillez consulter mon article précédent sur le sujet.

Résumé et recommandations

La mise en cache d'objets temporaires dans un module peut réduire considérablement la pression sur les structures d'allocation et de métadonnées partagées dans tempdb base de données. La plus grande réduction se produira lors de l'utilisation de variables de table, car la mise en cache et la réutilisation de ces objets temporaires n'impliquent aucunement la modification des métadonnées (aucune opération de changement de nom). Un conflit sur les structures d'allocation peut toujours être observé si la seule page de données en cache est insuffisante pour contenir toutes les données de la variable de table lors de l'exécution.

L'impact sur la qualité du plan dû au manque d'informations de cardinalité pour les variables de table peut être atténué en utilisant OPTION(RECOMPILE) ou l'indicateur de trace 2453 (disponible à partir de SQL Server 2012). Notez que ces atténuations ne donnent à l'optimiseur que des informations sur le nombre total de lignes dans la table.

Pour généraliser, variables de table sont mieux utilisées lorsque les données sont petites (idéalement dans une seule page de données pour un maximum d'avantages de conflit) et lorsque la sélection du plan ne dépend pas des valeurs présentes dans la variable de table.

Si des informations sur la distribution des données (densité et histogrammes) est important pour la sélection du plan, utilisez une table temporaire locale Au lieu. Assurez-vous de respecter les conditions de mise en cache des tables temporaires, ce qui signifie le plus souvent ne pas créer d'index ou de statistiques après l'instruction de création de table initiale. Ceci est rendu plus pratique à partir de SQL Server 2014 grâce à l'introduction de l'INDEX clause de la CREATE TABLE déclaration.

Un UPDATE STATISTICS explicite après le chargement des données dans la table temporaire, et OPTION (RECOMPILE) des conseils sur les instructions qui référencent la table peuvent être nécessaires pour produire tous les avantages attendus des tables temporaires mises en cache dans un module.

Il est important de n'utiliser des objets temporaires que lorsqu'ils produisent un bénéfice clair, le plus souvent en termes de qualité du plan. L'utilisation excessive, inefficace ou inutile d'objets temporaires peut conduire à tempdb contention, même lorsque la mise en cache d'objets temporaires est réalisée.

La mise en cache optimale des objets temporaires peut ne pas être suffisante pour réduire tempdb contention à des niveaux acceptables dans tous les cas, même lorsque les objets temporaires ne sont utilisés que lorsqu'ils sont pleinement justifiés. L'utilisation de variables de table en mémoire ou de tables en mémoire non durables peut fournir des solutions ciblées dans de tels cas, bien qu'il y ait toujours des compromis à faire, et aucune solution unique ne représente actuellement la meilleure option dans tous les cas.