Exemple de base de données pour l’OLTP en mémoire

S’applique à :SQL ServerAzure SQL Database

Vue d’ensemble

Cet exemple présente la fonctionnalité OLTP en mémoire. Il présente les tables à mémoire optimisée et les procédures stockées compilées nativement. De plus, il permet d’illustrer les avantages relatifs aux performances d’OLTP en mémoire.

Note

Pour afficher cette rubrique pour SQL Server 2014 (12.x), consultez Extensions à AdventureWorks pour illustrer l’OLTP en mémoire.

L’exemple permet de migrer cinq tables de la base de données AdventureWorks2022 vers des tables à mémoire optimisée. De plus, il comporte une charge de travail de démonstration pour le traitement des commandes client. Utilisez cette charge de travail de démonstration pour voir le gain de performances obtenu en utilisant l’OLTP en mémoire sur votre serveur.

Dans la description de l’exemple, nous abordons les compromis effectués durant la migration des tables vers l’OLTP en mémoire afin de prendre en compte les fonctionnalités qui ne sont pas (encore) prises en charge pour les tables à mémoire optimisée.

La documentation de l'exemple est structurée comme suit :

Prerequisites

  • SQL Server 2016 (13.x)

  • Pour tester les performances, un serveur avec des caractéristiques semblables dans votre environnement de production. Pour cet exemple en particulier, vous devez disposer d’au moins 16 Go de mémoire pour SQL Server. Pour obtenir des conseils généraux sur le matériel pour l’OLTP en mémoire, consultez le billet de blog suivant : Hardware considerations for In-Memory OLTP in SQL Server 2014

Installation de l’exemple OLTP en mémoire basé sur AdventureWorks

Procédez comme suit pour installer l'exemple :

  1. Téléchargez AdventureWorks2016_EXT.bak et SQLServer2016Samples.zip à partir de : https://github.com/microsoft/sql-server-samples/releases/tag/adventureworks dans un dossier local, par exemple C:\Temp.

  2. Restaurez la sauvegarde de base de données à l’aide de Transact-SQL ou DE SQL Server Management Studio :

    1. Identifiez le dossier cible et le nom du fichier de données, par exemple :

      'h:\DATA\AdventureWorks2022_Data.mdf'

    2. Identifiez le dossier cible et le nom du fichier journal, par exemple :

      'i:\DATA\AdventureWorks2022_log.ldf'

      1. Le fichier journal doit être placé sur un lecteur différent du fichier de données, idéalement un lecteur à faible latence tel qu'un stockage sur disque SSD ou PCIe, pour des performances maximales.

    Exemple de script T-SQL :

    RESTORE DATABASE [AdventureWorks2022]   
      FROM DISK = N'C:\temp\AdventureWorks2022.bak'   
        WITH FILE = 1,    
      MOVE N'AdventureWorks2022_Data' TO N'h:\DATA\AdventureWorks2022_Data.mdf',    
      MOVE N'AdventureWorks2022_Log' TO N'i:\DATA\AdventureWorks2022_log.ldf',  
      MOVE N'AdventureWorks2022_mod' TO N'h:\data\AdventureWorks2022_mod'  
     GO  
    
  3. Pour voir les exemples de scripts et la charge de travail, décompressez le fichier SQLServer2016Samples.zip dans un dossier local. Consultez le fichier In-Memory OLTP\readme.txt pour obtenir des instructions sur l’exécution de la charge de travail.

Description des exemples de tables et de procédures

L’exemple crée de nouvelles tables pour les produits et les commandes client en fonction des tables présentes dans AdventureWorks2022. Le schéma des nouvelles tables est similaire à celui des tables existantes, avec quelques différences, comme expliqué ci-dessous.

Les nouvelles tables à mémoire optimisée ont le suffixe « _inmem ». L’exemple inclut également les tables correspondantes avec le suffixe « _ondisk » : elles peuvent être utilisées pour effectuer une comparaison individuelle du niveau de performance des tables à mémoire optimisée et des tables sur disque de votre système.

Les tables à mémoire optimisée utilisées dans la charge de travail pour la comparaison du niveau de performance sont entièrement durables et entièrement journalisées. Elles ne sacrifient pas la durabilité ou la fiabilité pour atteindre les gains de performance.

La charge de travail cible pour cet exemple est le traitement des commandes, comprenant également des informations sur les produits et les remises. À cette fin, nous utilisons les tables SalesOrderHeader, SalesOrderDetail, Product, SpecialOffer et SpecialOfferProduct.

Deux nouvelles procédures stockées, Sales.usp_InsertSalesOrder_inmem et Sales.usp_UpdateSalesOrderShipInfo_inmem, sont utilisées pour insérer les commandes client et mettre à jour les informations d’expédition d’une commande spécifique.

Le nouveau schéma Demo contient les tables d’assistance et les procédures stockées permettant d’exécuter une charge de travail de démonstration.

Concrètement, l’exemple In-Memory OLTP ajoute les objets suivants dans AdventureWorks2022 :

Tables ajoutées par l'exemple

Nouvelles tables

Sales.SalesOrderHeader_inmem

  • Informations sur les en-têtes des commandes. Chaque commande possède une ligne dans cette table.

Sales.SalesOrderDetail_inmem

  • Détails des commandes. À chaque article d'une commande correspond une ligne dans cette table.

Sales.SpecialOffer_inmem

  • Informations sur les offres spéciales, y compris le pourcentage de remise associé à chaque offre spéciale.

Sales.SpecialOfferProduct_inmem

  • Table de référence qui relie les offres spéciales et les produits. Chaque offre spéciale peut contenir zéro ou plusieurs produits, et chaque produit peut être associé à zéro ou plusieurs offres spéciales.

Production.Product_inmem

  • Informations sur les produits, notamment leur prix catalogue.

Demo.DemoSalesOrderDetailSeed

  • Utilisé dans la charge de travail de démonstration pour construire des exemples de commandes.

Variations sur disque des tables :

  • Sales.SalesOrderHeader_ondisk

  • Sales.SalesOrderDetail_ondisk

  • Sales.SpecialOffer_ondisk

  • Sales.SpecialOfferProduct_ondisk

  • Production.Product_ondisk

Différences entre les tables sur disque d’origine et les nouvelles tables à mémoire optimisée

Pour la plupart, les nouvelles tables de cet exemple utilisent les mêmes colonnes et les mêmes types de données que les tables d'origine. Toutefois, il existe quelques différences. Nous les avons répertoriées ci-dessous, avec le raisonnement sous-jacent au changement.

Sales.SalesOrderHeader_inmem

  • Lescontraintes par défaut sont prises en charge pour les tables optimisées en mémoire, et la plupart des contraintes par défaut ont été migrées en l’état. Toutefois, la table Sales.SalesOrderHeader d’origine contient plusieurs contraintes par défaut qui récupèrent la date actuelle, pour les colonnes OrderDate et ModifiedDate. Dans une charge de travail de traitement des commandes à haut débit avec de nombreuses concurrences, toute ressource globale peut devenir un point de contention. L’heure système est l’une de ces ressources globales, et nous avons observé qu’elle peut devenir un goulot d’étranglement quand une charge de travail d’OLTP en mémoire qui insère des commandes client est exécutée, en particulier si l’heure système doit être extraite pour plusieurs colonnes dans l’en-tête de la commande, ainsi que pour ses détails. Le problème est résolu dans cet exemple en récupérant l’heure système une seule fois pour chaque commande client insérée, puis en utilisant cette valeur pour les colonnes DateHeure dans SalesOrderHeader_inmem et SalesOrderDetail_inmem, dans la procédure stockée Sales.usp_InsertSalesOrder_inmem.

  • Types de données définis par l’utilisateur (UDT, User-Defined Type) alias : la table d’origine utilise deux UDT alias, respectivement dbo.OrderNumber et dbo.AccountNumber pour les colonnes PurchaseOrderNumber et AccountNumber. SQL Server 2016 (13.x) ne prend pas en charge les alias UDT pour les tables optimisées en mémoire. Les nouvelles tables utilisent respectivement les types de données système nvarchar(25) et nvarchar(15).

  • Colonnes pouvant accepter la valeur Null dans l’index : dans la table d’origine, la colonne SalesPersonID accepte la valeur Null. Dans les nouvelles tables en revanche, la colonne n’accepte pas les valeurs Null et présente une contrainte par défaut avec la valeur (-1). Cela est dû au fait que les index des tables à mémoire optimisée ne peuvent pas avoir de colonnes Nullable dans la clé d’index ; -1 est une valeur de substitution de la valeur NULL dans ce cas.

  • Colonnes calculées : les colonnes SalesOrderNumber calculées et TotalDue sont omises, car SQL Server 2016 (13.x) ne prend pas en charge les colonnes calculées dans les tables mémoire optimisées. La nouvelle vue Sales.vSalesOrderHeader_extended_inmem reflète les colonnes SalesOrderNumber et TotalDue. Par conséquent, vous pouvez utiliser cette vue si ces colonnes sont nécessaires.

    • S’applique à : SQL Server 2017 (14.x) CTP 1.1.
      À compter de SQL Server 2017 (14.x) CTP 1.1, les colonnes calculées sont prises en charge dans les tables et index optimisés en mémoire.
  • Les contraintes de clé étrangère sont prises en charge pour les tables mémoire optimisées dans SQL Server 2016 (13.x), mais uniquement si les tables référencées sont également optimisées en mémoire. Les clés étrangères qui référencent des tables également migrées vers des tables optimisées en mémoire sont conservées dans les tables migrées, tandis que les autres clés étrangères sont omises. En outre, SalesOrderHeader_inmem est une table chaude dans l’exemple de charge de travail. Les contraintes de clé étrangère impliquent un traitement supplémentaire pour toutes les opérations DML, avec des recherches dans toutes les autres tables auxquelles ces contraintes font référence. Il est par conséquent supposé que l’application garantit l’intégrité référentielle de la table Sales.SalesOrderHeader_inmem et que l’intégrité référentielle n’est pas validée lorsque des lignes sont insérées.

  • Rowguid : la colonne ROWGUID est omise. Bien que uniqueidentifier soit pris en charge pour les tables optimisées en mémoire, l’option ROWGUIDCOL n’est pas prise en charge dans SQL Server 2016 (13.x). Les colonnes de ce type sont généralement utilisées pour la réplication de fusion ou pour des tables qui possèdent des colonnes FILESTREAM. Cet exemple ne comporte aucun de ces éléments.

Sales.SalesOrderDetail

  • Contraintes par défaut : tout comme SalesOrderHeader, la contrainte par défaut qui exige la date et l’heure système n’est pas migrée. En revanche, la procédure stockée d’insertion des commandes client se charge d’insérer la date et l’heure système actuelles à la première insertion.

  • Colonnes calculées : la colonne LineTotal calculée n’a pas été migrée, car les colonnes calculées ne sont pas prises en charge avec des tables optimisées en mémoire dans SQL Server 2016 (13.x). Pour accéder à cette colonne, utilisez la vue Sales.vSalesOrderDetail_extended_inmem.

  • Rowguid : la colonne rowguid est omise. Pour plus d’informations, consultez la description de la table SalesOrderHeader.

Production.Product

  • UDT alias : la table d’origine utilise le type de données défini par l’utilisateur dbo.Flag, qui est équivalent au bit de type de données système. La table migrée utilise le type de données bit à la place.

  • Rowguid : la colonne rowguid est omise. Pour plus d’informations, consultez la description de la table SalesOrderHeader.

Sales.SpecialOffer

  • Rowguid : la colonne rowguid est omise. Pour plus d’informations, consultez la description de la table SalesOrderHeader.

Sales.SpecialOfferProduct

  • Rowguid : la colonne rowguid est omise. Pour plus d’informations, consultez la description de la table SalesOrderHeader.

Observations sur les index des tables optimisées en mémoire

L'index de base des tables optimisées en mémoire est l'index non cluster, qui prend en charge les recherches de point (recherche d'index dans le prédicat d'égalité), les analyses de plage (recherche d'index dans l'attribut d'inégalité), les analyses d'index complet, et les analyses triées. En outre, les index non cluster prennent en charge la recherche dans les colonnes de début de la clé d'index. En fait, les index non cluster optimisés en mémoire autorisent toutes les opérations prises en charge par les index non cluster sur disque, à la seule exception des analyses ascendantes. Par conséquent, l'utilisation des index non cluster est un choix sûr pour les index.

Les index de HACHAGE peuvent être utilisés pour optimiser davantage la charge de travail. Ils sont spécialement adaptés aux recherches de point et aux insertions de lignes. Toutefois, il faut considérer qu'ils ne prennent pas en charge les analyses de plage, les analyses triées, ou la recherche sur les colonnes clés d'index. Par conséquent, leur utilisation est plus délicate. En outre, il est nécessaire de spécifier bucket_count lors de la création. Celui-ci doit généralement correspondre à une valeur comprise entre le nombre de valeurs de clé d'index et son double, mais il peut généralement être surestimé.

Pour plus d’informations :

Les index des tables migrées ont été paramétrés pour la charge de travail de traitement des commandes de démonstration. La charge de travail repose sur les insertions et les recherches de point dans les tables Sales.SalesOrderHeader_inmem et Sales.SalesOrderDetail_inmem. Elle s’appuie également sur les recherches de point sur les colonnes clés primaires dans les tables Production.Product_inmem et Sales.SpecialOffer_inmem.

Sales.SalesOrderHeader_inmem possède trois index, qui sont tous des index de HACHAGE pour des raisons de performances. Aucune analyse triée ni aucune analyse de plage n’est nécessaire pour la charge de travail.

  • Index de HACHAGE sur (SalesOrderID) : bucket_count est dimensionné à 10 millions (arrondi à 16 millions), car le nombre estimé de commandes client est de 10 millions.

  • Index de HACHAGE sur (SalesPersonID) : bucket_count est égal à 1 million. Le jeu de données fourni ne comporte pas beaucoup de commerciaux. Toutefois, ce grand bucket_count permet une augmentation future. De plus, vous n’êtes pas affecté par une dégradation du niveau de performance pour les recherches de points si le bucket_count est surdimensionné.

  • Index de HACHAGE sur (CustomerID) : bucket_count est égal à 1 million. Le jeu de données fourni ne contient pas beaucoup de clients, mais il permet une croissance future.

Sales.SalesOrderDetail_inmem possède trois index, qui sont tous des index de HACHAGE pour des raisons de performances. Aucune analyse triée ni aucune analyse de plage n’est nécessaire pour la charge de travail.

  • Index de HACHAGE sur (SalesOrderID, SalesOrderDetailID) : il s’agit de l’index de clé primaire. Bien que les recherches sur (SalesOrderID, SalesOrderDetailID) soient rares, l’utilisation d’un index de hachage pour la clé accélère les insertions de lignes. Le bucket_count est dimensionné à 50 millions (arrondi à 67 millions) : le nombre attendu de commandes client est de 10 millions, ce qui correspond à une moyenne de cinq articles par commande

  • Index de HACHAGE sur (SalesOrderID) : les recherches par commande client sont fréquentes, pour trouver tous les articles correspondant à une commande unique. Le Index bucket_count est dimensionné à 10 millions (arrondi à 16 millions), car le nombre estimé de commandes est 10 millions.

  • Index de HACHAGE sur (ProductID) : bucket_count est égal à 1 million. Le jeu de données fourni ne contient pas beaucoup de produits, mais il permet une croissance future.

Production.Product_inmem comporte trois index :

  • Index de HACHAGE sur (ProductID) : les recherches sur ProductID se trouvent dans le chemin critique de la charge de travail de démonstration. Il s’agit donc d’un index de hachage.

  • Index NON CLUSTER sur (Name) : il permet d’effectuer des analyses triées par nom de produit.

  • Index NON CLUSTER sur (ProductNumber) : il permet d’effectuer des analyses triées par numéro de produit.

Sales.SpecialOffer_inmem comporte un index de HACHAGE sur (SpecialOfferID) : les recherches de point d’offres spéciales se trouvent dans le chemin critique de la charge de travail de démonstration. bucket_count est dimensionné à 1 million pour permettre la croissance future.

Il n’est pas fait référence à Sales.SpecialOfferProduct_inmem dans la charge de travail de démonstration. Il n’y a donc aucun besoin apparent d’utiliser des index de hachage sur cette table pour optimiser la charge de travail. Les index sur (SpecialOfferID, ProductID) et (ProductID) sont des index NON CLUSTER.

Comme vous pouvez le constater ci-dessus, certaines valeurs bucket_count sont surdimensionnées, mais pas celles des index sur SalesOrderHeader_inmem et SalesOrderDetail_inmem, qui sont limitées à 10 millions de commandes client. Cela a pour but de permettre l'installation de l'exemple sur des systèmes avec une faible disponibilité de mémoire ; cependant dans ces cas, la charge de travail de démonstration échoue pour conditions de mémoire insuffisante. Si vous voulez dimensionner au-delà de 10 millions de commandes, augmentez le nombre de compartiments en conséquence.

Observations sur l'utilisation de la mémoire

L'utilisation de la mémoire dans la base de données d'exemple, avant et après l'exécution de la charge de travail de démonstration, est décrite dans la section Utilisation de la mémoire pour les tables optimisées en mémoire.

Procédures stockées ajoutées par l'exemple

Les deux procédures stockées clés d'insertion des commandes et de mise à jour des informations d'expédition sont les suivantes :

  • Sales.usp_InsertSalesOrder_inmem

    • Insère une nouvelle commande client dans la base de données et génère le SalesOrderID de cette commande client. Comme paramètres d'entrée, elle récupère les détails de l'en-tête de la commande client, ainsi que les articles de la commande.

    • Paramètre de sortie :

      • @SalesOrderID int : SalesOrderID de la commande client qui vient d’être insérée.
    • Paramètres d'entrée (obligatoires) :

      • @DueDate datetime2

      • @CustomerID int

      • @BillToAddressID [int]

      • @ShipToAddressID [int]

      • @ShipMethodID [int]

      • @SalesOrderDetailsSales.SalesOrderDetailType_inmem - paramètre table (TVP) qui contient les éléments de ligne de l’ordre

    • Paramètres d'entrée (facultatifs) :

      • @Status [tinyint]

      • @OnlineOrderFlag [bit]

      • @PurchaseOrderNumber [nvarchar] (25)

      • @AccountNumber [nvarchar] (15)

      • @SalesPersonID [int]

      • @TerritoryID [int]

      • @CreditCardID [int]

      • @CreditCardApprovalCode [varchar] (15)

      • @CurrencyRateID [int]

      • @Comment nvarchar(128)

  • Sales.usp_UpdateSalesOrderShipInfo_inmem

    • Met à jour les informations d'expédition d'une commande client spécifique. Met également à jour les informations d'expédition de tous les articles de la commande.

    • Il s’agit d’une procédure wrapper pour les procédures stockées compilées en mode natif Sales.usp_UpdateSalesOrderShipInfo_native avec une logique de nouvelle tentative permettant de traiter les conflits potentiels (inattendus) avec des transactions simultanées qui mettent à jour la même commande. Pour plus d’informations, consultez Logique de nouvelle tentative.

  • Sales.usp_UpdateSalesOrderShipInfo_native

    • Il s'agit de la procédure stockée compilée en mode natif qui traite effectivement la mise à jour des informations d'expédition. Elle doit être appelée à partir de la procédure stockée wrapper Sales.usp_UpdateSalesOrderShipInfo_inmem. Si le client peut traiter les échecs et implémente la logique de nouvelle tentative, vous pouvez appeler cette procédure directement, au lieu d'utiliser la procédure stockée wrapper.

La procédure stockée suivante est utilisée pour la charge de travail de démonstration.

  • Demo.usp_DemoReset

    • Réinitialise la démonstration en vidant et en réamorçant les tables SalesOrderHeader et SalesOrderDetail.

Les procédures stockées suivantes sont utilisées pour insérer et supprimer des tables optimisées en mémoire tout en garantissant l'intégrité du domaine et l'intégrité référentielle.

  • Production.usp_InsertProduct_inmem

  • Production.usp_DeleteProduct_inmem

  • Sales.usp_InsertSpecialOffer_inmem

  • Sales.usp_DeleteSpecialOffer_inmem

  • Sales.usp_InsertSpecialOfferProduct_inmem

Enfin, la procédure stockée suivante est utilisée pour vérifier l'intégrité du domaine et l'intégrité référentielle.

  1. dbo.usp_ValidateIntegrity

    • Paramètre facultatif : @object_id - ID de l’objet dont l’intégrité doit être validée

    • Cette procédure s’appuie sur les tables dbo.DomainIntegrity, dbo.ReferentialIntegrity et dbo.UniqueIntegrity pour les règles d’intégrité qui doivent être vérifiées. L’exemple remplit ces tables en fonction des contraintes CHECK, de clé étrangère et d’unicité qui existent pour les tables d’origine dans la base de données AdventureWorks2022.

    • Elle repose sur les procédures d’assistance dbo.usp_GenerateCKCheck, dbo.usp_GenerateFKCheck, et dbo.GenerateUQCheck pour générer l’instruction T-SQL nécessaire aux vérifications de l’intégrité.

Mesures de performance à l'aide de la charge de travail de démonstration

Ostress est un outil en ligne de commande qui a été développé par l’équipe de support technique de Microsoft CSS SQL Server. Cet outil peut être utilisé pour exécuter des requêtes ou des procédures stockées distantes en parallèle. Vous pouvez configurer le nombre de threads pour exécuter une instruction T-SQL donnée en parallèle et spécifier combien de fois l'instruction doit être exécutée sur ce thread. Ostress assemblera les threads et exécutera l'instruction sur tous les threads en parallèle. Lorsque l'exécution est terminée pour tous les threads, Ostress indique le temps qu'il a fallu pour terminer l'exécution sur tous les threads.

Installation d'Ostress

Ostress est installé avec les utilitaires RML (Report Markup Language). Il n’existe aucune installation autonome pour ostress.

Étapes d'installation :

  1. Téléchargez et exécutez le package d’installation x64 pour les utilitaires RML à partir de la page suivante : Télécharger RML pour SQL Server

  2. Si une boîte de dialogue indique que certains fichiers sont en cours d’utilisation, sélectionnez « Continuer ».

Exécution d'Ostress

Ostress s'exécute à partir de l'invite de ligne de commande. Il est plus pratique d'exécuter l'outil à partir de l'« invite de commandes RML », qui est installé avec les Utilitaires RML.

Pour ouvrir l’invite de commandes RML, procédez comme suit :

Sur Windows, ouvrez le menu Démarrer en sélectionnant la clé Windows, puis tapez rml. Sélectionnez « Invite cmd RML », qui se trouvera dans la liste des résultats de la recherche.

Vérifiez que l'invite de commandes se trouve dans le dossier d'installation des utilitaires RML.

Les options de ligne de commande d'Ostress s'affichent en exécutant simplement ostress.exe, sans besoin d'aucune option de ligne de commande. Les options principales à prendre en compte pour exécuter Ostress avec cet exemple sont les suivantes :

  • -S Nom de l’instance de Microsoft SQL Server à laquelle se connecter

  • -E Utiliser l’authentification Windows pour la connexion (valeur par défaut) ; si vous utilisez l’authentification SQL Server, utilisez les options -U et -P pour spécifier le nom d’utilisateur et le mot de passe, respectivement

  • -d Nom de la base de données, dans cet exemple AdventureWorks2022

  • -Q Instruction T-SQL à exécuter

  • -n Nombre de connexions qui traitent chaque fichier d'entrée/requête

  • -r Nombre d'itérations pour chaque connexion qui exécute chaque fichier d'entrée/requête

Charge de travail de démonstration

La procédure stockée principale utilisée dans la charge de travail de démonstration est Sales.usp_InsertSalesOrder_inmem/ondisk. Le script ci-dessous construit un paramètre table (TVP) avec des exemples de données, puis appelle la procédure pour insérer une commande client avec cinq articles.

L'outil Ostress permet d'exécuter des appels de procédure stockée en parallèle, pour simuler des clients insérant des commandes simultanément.

Réinitialisez la démonstration après chaque exécution contrainte en exécutant Demo.usp_DemoReset. Cette procédure supprime les lignes des tables optimisées en mémoire, tronque les tables sur disque, et exécute un point de contrôle de base de données.

Le script suivant est exécuté simultanément pour simuler une charge de travail de traitement des commandes :

DECLARE   
      @i int = 0,   
      @od Sales.SalesOrderDetailType_inmem,   
      @SalesOrderID int,   
      @DueDate datetime2 = sysdatetime(),   
      @CustomerID int = rand() * 8000,   
      @BillToAddressID int = rand() * 10000,   
      @ShipToAddressID int = rand() * 10000,   
      @ShipMethodID int = (rand() * 5) + 1;   
  
INSERT INTO @od   
SELECT OrderQty, ProductID, SpecialOfferID   
FROM Demo.DemoSalesOrderDetailSeed   
WHERE OrderID= cast((rand()*106) + 1 as int);   
  
WHILE (@i < 20)   
BEGIN;   
      EXEC Sales.usp_InsertSalesOrder_inmem @SalesOrderID OUTPUT, @DueDate, @CustomerID, @BillToAddressID, @ShipToAddressID, @ShipMethodID, @od;   
      SET @i += 1   
END

Avec ce script, chaque exemple de commande construite est inséré 20 fois, via 20 procédures stockées exécutées dans une boucle WHILE. La boucle est utilisée pour tenir compte du fait que la base de données est utilisée pour construire l'exemple de commande. Dans des environnements de production standard, l'application de niveau intermédiaire construira la commande à insérer.

Le script ci-dessus insère des commandes dans les tables optimisées en mémoire. Le script pour insérer des commandes dans les tables sur disque est dérivé en remplaçant les deux occurrences de « _inmem » par « _ondisk ».

Nous utiliserons l'outil Ostress pour exécuter des scripts utilisant plusieurs connexions simultanées. Nous utiliserons le paramètre « -n » pour contrôler le nombre de connexions, et le paramètre « r » pour contrôler le nombre de fois où le script est exécuté sur chaque connexion.

Exécution de la charge de travail

Pour tester à l'échelle, nous insérons 10 millions de commandes, à l'aide de 100 connexions. Ce test peut être exécuté aisément sur un serveur de petite taille (par exemple 8 noyaux physiques et 16 noyaux logiques) et un stockage SSD de base pour le journal. Si le test ne fonctionne pas correctement sur votre matériel, consultez la section Résolution des problèmes de lenteur d’exécution des tests. Si vous voulez réduire le niveau de contrainte de ce test, diminuez le nombre de connexions en modifiant le paramètre « -n ». Par exemple, pour réduire le nombre de connexions à 40, remplacez le paramètre « -n100 » par « -n40 ».

Comme mesure de performances pour la charge de travail, nous utilisons le temps écoulé tel qu'indiqué par ostress.exe après avoir exécuté la charge de travail.

Les instructions et les mesures ci-dessous utilisent une charge de travail qui insère 10 millions de commandes. Pour obtenir des instructions sur l’exécution d’une charge de travail réduite à l’insertion de 1 million de commandes, consultez les instructions figurant dans le fichier « In-Memory OLTP\readme.txt » qui fait partie de l’archive SQLServer2016Samples.zip.

Tables optimisées en mémoire

Nous allons commencer par exécuter la charge de travail sur les tables optimisées en mémoire. La commande suivante ouvre 100 threads, chacun exécuté pour 5 000 itérations. Chaque itération insère 20 commandes dans des transactions séparées. Il y a 20 insertions par itération pour compenser le fait que la base de données est utilisée pour générer les données à insérer. Cela génère un total de 20 * 5 000 * 100 = 10 000 000 insertions de commandes.

Ouvrez l'invite de commandes RML et exécutez la commande suivante :

Sélectionnez le bouton Copier pour copier la commande, puis collez-la dans l’invite de commandes Utilitaires RML.

ostress.exe -n100 -r5000 -S. -E -dAdventureWorks2022 -q -Q"DECLARE @i int = 0, @od Sales.SalesOrderDetailType_inmem, @SalesOrderID int, @DueDate datetime2 = sysdatetime(), @CustomerID int = rand() * 8000, @BillToAddressID int = rand() * 10000, @ShipToAddressID int = rand() * 10000, @ShipMethodID int = (rand() * 5) + 1; INSERT INTO @od SELECT OrderQty, ProductID, SpecialOfferID FROM Demo.DemoSalesOrderDetailSeed WHERE OrderID= cast((rand()*106) + 1 as int); while (@i < 20) begin; EXEC Sales.usp_InsertSalesOrder_inmem @SalesOrderID OUTPUT, @DueDate, @CustomerID, @BillToAddressID, @ShipToAddressID, @ShipMethodID, @od; set @i += 1 end"  

Sur un serveur de test avec un nombre total de 8 noyaux physiques (16 logiques), ceci a nécessité 2 minutes et 5 secondes. Sur un second serveur de test avec 24 noyaux physiques (48 logiques), ceci a nécessité 1 minute et 0 secondes.

Observez l'utilisation de l'UC pendant que la charge de travail est exécutée, par exemple via le Gestionnaire des tâches. Vous constaterez que l'utilisation de l'UC est proche de 100 %. Dans le cas contraire, vous avez un goulot d'étranglement d'E/S du journal. Consultez Dépannage des tests lents.

Tables sur disque

La commande suivante exécute la charge de travail sur les tables sur disque. Cette charge de travail peut prendre un certain temps pour s’exécuter, principalement en raison d’une contention de verrous dans le système. Les tables optimisées en mémoire n'ont pas de verrous et ne sont pas concernées par ce problème.

Ouvrez l'invite de commandes RML et exécutez la commande suivante :

Sélectionnez le bouton Copier pour copier la commande, puis collez-la dans l’invite de commandes Utilitaires RML.

ostress.exe -n100 -r5000 -S. -E -dAdventureWorks2022 -q -Q"DECLARE @i int = 0, @od Sales.SalesOrderDetailType_ondisk, @SalesOrderID int, @DueDate datetime2 = sysdatetime(), @CustomerID int = rand() * 8000, @BillToAddressID int = rand() * 10000, @ShipToAddressID int = rand() * 10000, @ShipMethodID int = (rand() * 5) + 1; INSERT INTO @od SELECT OrderQty, ProductID, SpecialOfferID FROM Demo.DemoSalesOrderDetailSeed WHERE OrderID= cast((rand()*106) + 1 as int); while (@i < 20) begin; EXEC Sales.usp_InsertSalesOrder_ondisk @SalesOrderID OUTPUT, @DueDate, @CustomerID, @BillToAddressID, @ShipToAddressID, @ShipMethodID, @od; set @i += 1 end"  

Sur un serveur de test avec un nombre total de 8 noyaux physiques (16 logiques), ceci a nécessité 41 minutes et 25 secondes. Sur un second serveur de test avec 24 noyaux physiques (48 logiques), ceci a nécessité 52 minutes et 16 secondes.

La raison principale de la différence de performances entre les tables optimisées en mémoire et les tables sur disque pendant ce test, est que quand vous utilisez des tables sur disque, SQL Server n’utilise pas entièrement l’UC. Cela est dû à une contention de verrou : les transactions simultanées tentent d'écrire dans la même page de données ; les verrous sont utilisés pour garantir qu'une seule transaction à la fois écrit sur une page. Le moteur d’OLTP en mémoire n’a pas de verrous, et les lignes de données ne sont pas organisées en pages. Ainsi, les transactions simultanées ne bloquent pas les insertions réciproques, ce qui permet à SQL Server d’utiliser pleinement le processeur.

Observez l'utilisation de l'UC pendant que la charge de travail est exécutée, par exemple via le Gestionnaire des tâches. Vous verrez qu'avec les tables sur disque, l'utilisation de l'UC est loin d'être de 100 %. Dans une configuration de test avec 16 processeurs logiques, l'utilisation serait d'environ 24 %.

Vous pouvez si vous le souhaitez afficher le nombre d’attentes de verrou interne par seconde à l’aide de l’Analyseur de performances, avec le compteur de performances \SQL Server:Latches\Latch Waits/sec.

Réinitialisation de la démonstration

Pour réinitialiser la démonstration, ouvrez l'invite de commandes RML et exécutez la commande suivante :

ostress.exe -S. -E -dAdventureWorks2022 -Q"EXEC Demo.usp_DemoReset"  

Selon le matériel, l’exécution peut prendre quelques minutes.

Nous vous recommandons de réinitialiser après chaque exécution de démonstration. Étant donné que cette charge de travail est réservée à l'insertion, chaque exécution consommera plus de mémoire, c'est pourquoi une réinitialisation est requise afin d'éviter des conditions de mémoire insuffisante. La quantité de mémoire consommée après une exécution est décrite dans la section Utilisation de la mémoire après avoir exécuté la charge de travail.

Dépannage des tests lents

Les résultats des tests varient généralement selon le matériel, mais aussi selon le niveau de concurrence utilisé dans l'exécution du test. Voici quelques pistes à explorer, si les résultats ne sont pas tels que prévu :

  • Nombre de transactions simultanées : lors de l’exécution de la charge de travail sur un seul thread, le gain de performances avec l’OLTP en mémoire sera probablement inférieur à 2X. La contention de verrou ne constitue véritablement un problème qu’en cas de niveau de concurrence élevé.

  • Nombre faible de noyaux disponibles pour SQL Server : cela signifie qu’il y aura un niveau de concurrence faible dans le système, car il ne peut y avoir qu’autant de transactions simultanée en cours d’exécution qu’il y a de noyaux disponibles pour SQL.

    • Symptôme : si l'utilisation de l'UC est élevée lors de l'exécution de la charge de travail sur les tables sur disque, cela signifie qu'il n'y a pas beaucoup de contentions, et donc qu'il n'y a pas de concurrence.
  • Vitesse du lecteur de journalisation : si le lecteur de journalisation n'arrive pas à suivre le débit des transactions dans le système, la charge de travail est congestionnée dans le journal des E/S. Bien que la journalisation soit plus efficace avec l’OLTP en mémoire, si le journal des E/S est congestionné, le gain de performance potentiel est limité.

    • Symptôme : si l'utilisation de l'UC n'est pas proche de 100 % ou varie beaucoup pendant l'exécution de la charge de travail sur les tables optimisées en mémoire, il est possible qu'il existe un goulot d'étranglement du journal des E/S. Cela peut être vérifié en ouvrant le moniteur de ressource et en examinant la longueur de la file d'attente du lecteur de journalisation.

Utilisation de la mémoire et de l’espace disque dans l’exemple

Vous trouverez ci-dessous la description de ce à quoi vous devez vous attendre en termes d'utilisation de la mémoire et de l'espace disque pour l'exemple de base de données. Nous présentons également les résultats obtenus pour un serveur de test avec 16 noyaux logiques.

Utilisation de la mémoire pour les tables optimisées en mémoire

Utilisation générale de la base de données

La requête suivante peut être utilisée pour obtenir l’utilisation totale de mémoire pour l’OLTP en mémoire dans le système.

SELECT type  
   , name  
, pages_kb/1024 AS pages_MB   
FROM sys.dm_os_memory_clerks WHERE type LIKE '%xtp%'  

Instantané de la base de données juste après sa création :

type nom pages_MB
MEMORYCLERK_XTP Default 94
MEMORYCLERK_XTP DB_ID_5 877
MEMORYCLERK_XTP Default 0
MEMORYCLERK_XTP Default 0

Les régisseurs de mémoire par défaut contiennent les structures de mémoire à l'échelle du système et sont relativement petits. Le régisseur de mémoire de la base de données utilisateur, dans le cas présent la base de données qui possède l’ID 5 (database_id peut être différent dans votre instance), présente une taille d’environ 900 Mo.

Utilisation de la mémoire par table

La requête suivante peut être utilisée pour explorer l'utilisation de la mémoire des différentes tables et de leurs index :

SELECT object_name(t.object_id) AS [Table Name]  
     , memory_allocated_for_table_kb  
 , memory_allocated_for_indexes_kb  
FROM sys.dm_db_xtp_table_memory_stats dms JOIN sys.tables t   
ON dms.object_id=t.object_id  
WHERE t.type='U';  

Le tableau suivant affiche les résultats de cette requête pour une nouvelle installation de l’exemple :

Nom de table memory_allocated_for_table_kb memory_allocated_for_indexes_kb
SpecialOfferProduct_inmem 64 3840
DemoSalesOrderHeaderSeed 1984 5504
SalesOrderDetail_inmem 15316 663552
DemoSalesOrderDetailSeed 64 10432
SpecialOffer_inmem 3 8 192
SalesOrderHeader_inmem 7168 147456
Product_inmem 124 12352

Comme vous pouvez le constater, les tables sont assez petites : SalesOrderHeader_inmem a une taille d’environ 7 Mo et SalesOrderDetail_inmem une taille d’environ 15 Mo.

Ce qui est frappant ici est la taille de la mémoire allouée aux index, par rapport à la taille des données de table. Cela est dû au fait que les index de hachage de l'exemple sont prédimensionnés pour contenir plus de données. Notez que les index de hachage ont une taille fixe, par conséquent, leur taille n'augmente pas selon la taille des données de la table.

Utilisation de la mémoire après avoir exécuté la charge de travail

Après l'insertion de 10 millions de commandes, l'utilisation globale de la mémoire devrait s'apparenter à ce qui suit :

SELECT type  
, name  
, pages_kb/1024 AS pages_MB   
FROM sys.dm_os_memory_clerks WHERE type LIKE '%xtp%'  
type nom pages_MB
MEMORYCLERK_XTP Default 146
MEMORYCLERK_XTP DB_ID_5 7374
MEMORYCLERK_XTP Default 0
MEMORYCLERK_XTP Default 0

Comme vous pouvez le voir, SQL Server utilise un peu moins de 8 Go pour les tables à mémoire optimisée et les index dans l’exemple de base de données.

Voici l'utilisation de la mémoire détaillée par table après l'exécution d'un exemple :

SELECT object_name(t.object_id) AS [Table Name]  
     , memory_allocated_for_table_kb  
 , memory_allocated_for_indexes_kb  
FROM sys.dm_db_xtp_table_memory_stats dms JOIN sys.tables t   
ON dms.object_id=t.object_id  
WHERE t.type='U'  
Nom de table memory_allocated_for_table_kb memory_allocated_for_indexes_kb
SalesOrderDetail_inmem 5113761 663552
DemoSalesOrderDetailSeed 64 10368
SpecialOffer_inmem 2 8 192
SalesOrderHeader_inmem 1575679 147456
Product_inmem 111 12032
SpecialOfferProduct_inmem 64 3712
DemoSalesOrderHeaderSeed 1984 5504

Nous pouvons voir un total d’environ 6,5 Go de données. Notez que la taille des index sur les tables SalesOrderHeader_inmem et SalesOrderDetail_inmem est la même qu’avant insertion des commandes client. La taille de l'index n'a pas changé car les deux tables utilisent des index de hachage, qui sont statiques.

Après la réinitialisation de la démonstration

La procédure stockée Demo.usp_DemoReset peut être utilisée pour réinitialiser la démonstration. Elle supprime les données des tables SalesOrderHeader_inmem et SalesOrderDetail_inmem, et réamorce les données des tables SalesOrderHeader et SalesOrderDetail d’origine.

Cependant, même si les lignes des tables ont été supprimées, cela ne signifie pas pour autant que la mémoire est immédiatement récupérée. SQL Server récupère la mémoire des lignes supprimées dans les tables optimisées en mémoire en arrière-plan, si nécessaire. Vous verrez qu'immédiatement après la réinitialisation de la démonstration, sans charge de travail transactionnelle sur le système, la mémoire des lignes supprimées n'est pas encore récupérée :

SELECT type  
, name  
, pages_kb/1024 AS pages_MB   
FROM sys.dm_os_memory_clerks WHERE type LIKE '%xtp%';
type nom pages_MB
MEMORYCLERK_XTP Default 2261
MEMORYCLERK_XTP DB_ID_5 7396
MEMORYCLERK_XTP Default 0
MEMORYCLERK_XTP Default 0

C'est le comportement attendu : la mémoire est récupérée lorsque la charge de travail transactionnelle s'exécute.

Si vous démarrez une deuxième exécution de la charge de travail de démonstration, vous verrez que l'utilisation de la mémoire diminue au début, au fur et à mesure que les lignes précédemment supprimées sont nettoyées. À un certain moment, la taille de la mémoire augmentera de nouveau, jusqu'à ce que la charge de travail soit terminée. Une fois que les 10 millions de lignes ont été insérées après la réinitialisation de la démonstration, l'utilisation de la mémoire sera très similaire à l'utilisation après la première exécution. Par exemple :

SELECT type  
, name  
, pages_kb/1024 AS pages_MB   
FROM sys.dm_os_memory_clerks WHERE type LIKE '%xtp%';
type nom pages_MB
MEMORYCLERK_XTP Default 1863
MEMORYCLERK_XTP DB_ID_5 7390
MEMORYCLERK_XTP Default 0
MEMORYCLERK_XTP Default 0

Utilisation du disque pour les tables optimisées en mémoire

La taille globale sur disque des fichiers de point de contrôle d'une base de données à un moment donné peut être récupérée à l'aide de la requête :

SELECT SUM(df.size) * 8 / 1024 AS [On-disk size in MB]  
FROM sys.filegroups f JOIN sys.database_files df   
   ON f.data_space_id=df.data_space_id  
WHERE f.type=N'FX';  
  

InitialState

Quand les exemples de groupe de fichiers et de tables à mémoire optimisée sont initialement créés, un certain nombre de fichiers de point de contrôle sont créés au préalable et le système commence à les remplir. Le nombre de fichiers de point de contrôle créés au préalable dépend du nombre de processeurs logiques dans le système. Étant donné que cet exemple a une taille très petite au début, les fichiers créés au préalable seront vides après la création initiale.

Le code suivant montre la taille initiale sur disque de l’exemple sur une machine dotée de 16 processeurs logiques :

SELECT SUM(df.size) * 8 / 1024 AS [On-disk size in MB]  
FROM sys.filegroups f JOIN sys.database_files df   
   ON f.data_space_id=df.data_space_id  
WHERE f.type=N'FX';  
Taille sur disque en Mo
2312

Comme vous pouvez le voir, il existe une grande différence entre la taille sur disque des fichiers de point de contrôle, qui est de 2,3 Go, et la taille réelle des données, qui est plus proche de 30 Mo.

Pour analyser de plus près la raison de l'utilisation de l'espace disque, vous pouvez utiliser la requête suivante, La taille du disque retournée par cette requête est approximative pour les fichiers ayant l'état 5 (REQUIRED FOR BACKUP/HA), 6 (IN TRANSITION TO TOMBSTONE) ou 7 (TOMBSTONE).

SELECT state_desc  
 , file_type_desc  
 , COUNT(*) AS [count]  
 , SUM(CASE  
   WHEN state = 5 AND file_type=0 THEN 128*1024*1024  
   WHEN state = 5 AND file_type=1 THEN 8*1024*1024  
   WHEN state IN (6,7) THEN 68*1024*1024  
   ELSE file_size_in_bytes  
    END) / 1024 / 1024 AS [on-disk size MB]   
FROM sys.dm_db_xtp_checkpoint_files  
GROUP BY state, state_desc, file_type, file_type_desc  
ORDER BY state, file_type;

Pour l'état initial de l'exemple, le résultat sera similaire à ce qui suit pour un serveur avec 16 processeurs logiques :

state_desc file_type_desc count taille sur disque en Mo
PRECREATED DONNÉES 16 2 048
PRECREATED DELTA 16 128
UNDER CONSTRUCTION DONNÉES 1 128
UNDER CONSTRUCTION DELTA 1 8

Comme vous pouvez le voir, la majeure partie de l'espace est utilisé par les fichiers de données et delta précréés. SQL Server créé au préalable une paire de fichiers (données, delta) par processeur logique. De plus, les fichiers de données sont prédimensionnés à 128 Mo, et les fichiers delta à 8 Mo, afin d’optimiser l’insertion des données dans ces fichiers.

Les données réelles dans les tables optimisées en mémoire se trouvent dans un seul fichier de données.

Après l'exécution de la charge de travail

Après une seule exécution de test qui insère 10 millions de commandes, la taille totale sur disque ressemble à ce qui suit (pour un serveur de test avec 16 noyaux) :

SELECT SUM(df.size) * 8 / 1024 AS [On-disk size in MB]  
FROM sys.filegroups f JOIN sys.database_files df   
   ON f.data_space_id=df.data_space_id  
WHERE f.type=N'FX';  
Taille sur disque en Mo
8828

La taille sur disque est proche de 9 Go, ce qui est proche de la taille en mémoire des données.

Analysons plus en détail les tailles des fichiers de point de contrôle entre les différents états :

SELECT state_desc  
 , file_type_desc  
 , COUNT(*) AS [count]  
 , SUM(CASE  
   WHEN state = 5 AND file_type=0 THEN 128*1024*1024  
   WHEN state = 5 AND file_type=1 THEN 8*1024*1024  
   WHEN state IN (6,7) THEN 68*1024*1024  
   ELSE file_size_in_bytes  
    END) / 1024 / 1024 AS [on-disk size MB]   
FROM sys.dm_db_xtp_checkpoint_files  
GROUP BY state, state_desc, file_type, file_type_desc  
ORDER BY state, file_type;
state_desc file_type_desc count taille sur disque en Mo
PRECREATED DONNÉES 16 2 048
PRECREATED DELTA 16 128
UNDER CONSTRUCTION DONNÉES 1 128
UNDER CONSTRUCTION DELTA 1 8

Nous avons toujours 16 paires de fichiers précréés, prêtes au fur et à mesure que les points de contrôle se ferment.

Il y a une paire en cours de création, utilisée tant que le point de contrôle actif n'est pas fermé. Avec les fichiers de point de contrôle actifs, cela donne environ 6,5 Go d’utilisation du disque pour 6,5 Go de données en mémoire. N'oubliez pas que les index ne sont pas conservés sur le disque, donc, dans ce cas, la taille globale sur le disque est plus petite que la taille de la mémoire.

Après la réinitialisation de la démonstration

Après la réinitialisation de la démonstration, l'espace disque n'est pas libéré immédiatement s'il n'y a pas de charge de travail transactionnelle sur le système, et s'il n'y a pas de points de contrôle de base de données. Pour que les fichiers de point de contrôle passent par les différentes étapes et soient inévitablement supprimés, plusieurs points de contrôle et événements de troncation du journal doivent se produire, pour initialiser la fusion des fichiers de point de contrôle, ainsi que pour initialiser le garbage collection. Cela se produit automatiquement si vous disposez d’une charge de travail transactionnelle dans le système (et que vous effectuez régulièrement des sauvegardes de fichier journal, dans le cas où vous utilisez le mode de restauration COMPLÈTE), mais pas lorsque le système est inactif, par exemple dans un scénario de démonstration.

Dans l’exemple, après la réinitialisation de la démonstration, vous obtenez un résultat de ce type :

SELECT SUM(df.size) * 8 / 1024 AS [On-disk size in MB]  
FROM sys.filegroups f JOIN sys.database_files df   
   ON f.data_space_id=df.data_space_id  
WHERE f.type=N'FX';
Taille sur disque en Mo
11839

À presque 12 Go, cela dépasse nettement les 9 Go que nous avions avant la réinitialisation de la démonstration. Cela est dû au fait que certaines fusions de fichiers de point de contrôle ont commencé, tandis que certaines cibles de fusion n'ont pas encore été installées, et que certains fichiers sources de fusion n'ont pas encore été nettoyés, comme vous pouvez le voir à partir des éléments suivants :

SELECT state_desc  
 , file_type_desc  
 , COUNT(*) AS [count]  
 , SUM(CASE  
   WHEN state = 5 AND file_type=0 THEN 128*1024*1024  
   WHEN state = 5 AND file_type=1 THEN 8*1024*1024  
   WHEN state IN (6,7) THEN 68*1024*1024  
   ELSE file_size_in_bytes  
    END) / 1024 / 1024 AS [on-disk size MB]   
FROM sys.dm_db_xtp_checkpoint_files  
GROUP BY state, state_desc, file_type, file_type_desc  
ORDER BY state, file_type;
state_desc file_type_desc count taille sur disque en Mo
PRECREATED DONNÉES 16 2 048
PRECREATED DELTA 16 128
ACTIF DONNÉES 38 5152
ACTIF DELTA 38 1331
MERGE TARGET DONNÉES 7 896
MERGE TARGET DELTA 7 56
MERGED SOURCE DONNÉES 13 1772
MERGED SOURCE DELTA 13 455

Les cibles de fusion sont installées et la source fusionnée est nettoyée au fur et à mesure que l'activité transactionnelle s'exécute dans le système.

Après une deuxième exécution de la charge de travail de démonstration, et l'insertion de 10 millions de commandes client après la réinitialisation de la démonstration, vous constaterez que les fichiers construits lors de la première exécution de la charge de travail ont été nettoyés. Si vous exécutez la requête ci-dessus plusieurs fois pendant que la charge de travail s'exécute, vous verrez les fichiers de point de contrôle passer à travers les différentes étapes.

Après la deuxième exécution de la charge de travail et l'insertion de 10 millions de commandes, vous verrez que l'utilisation du disque est très similaire, mais pas nécessairement identique, à celle constatée après la première exécution, car le système est dynamique par nature. Par exemple :

SELECT state_desc  
 , file_type_desc  
 , COUNT(*) AS [count]  
 , SUM(CASE  
   WHEN state = 5 AND file_type=0 THEN 128*1024*1024  
   WHEN state = 5 AND file_type=1 THEN 8*1024*1024  
   WHEN state IN (6,7) THEN 68*1024*1024  
   ELSE file_size_in_bytes  
    END) / 1024 / 1024 AS [on-disk size MB]   
FROM sys.dm_db_xtp_checkpoint_files  
GROUP BY state, state_desc, file_type, file_type_desc  
ORDER BY state, file_type;
state_desc file_type_desc count taille sur disque en Mo
PRECREATED DONNÉES 16 2 048
PRECREATED DELTA 16 128
UNDER CONSTRUCTION DONNÉES 2 268
UNDER CONSTRUCTION DELTA 2 16
ACTIF DONNÉES 41 5608
ACTIF DELTA 41 328

Dans ce cas, il existe deux paires de fichiers de point de contrôle avec l’état « under construction », signifiant que plusieurs paires de fichiers ont été déplacées vers l’état « under construction », probablement en raison du haut niveau de concurrence dans la charge de travail. Plusieurs threads simultanés ont nécessité une nouvelle paire de fichiers en même temps ; par conséquent, une paire est passée de l’état « precreated » à l’état « under construction ».

Étapes suivantes