Vous avez probablement déjà entendu dire à plusieurs reprises que SQL Server fournit une garantie pour les propriétés de transaction ACID. Cet article se concentre sur la partie D, qui est bien sûr synonyme de durabilité. Plus précisément, cet article se concentre sur un aspect de l'architecture de journalisation de SQL Server qui applique la durabilité des transactions :les vidages de la mémoire tampon du journal. Je parle de la fonction que remplit le tampon de journal, des conditions qui obligent SQL Server à vider le tampon de journal sur le disque, de ce que vous pouvez faire pour optimiser les performances des transactions, ainsi que des technologies associées récemment ajoutées telles que la durabilité retardée et la mémoire de classe de stockage non volatile.
Vidanges de la mémoire tampon du journal
La partie D dans les propriétés de transaction ACID représente la durabilité. Au niveau logique, cela signifie que lorsqu'une application envoie à SQL Server une instruction pour valider une transaction (explicitement ou avec une transaction de validation automatique), SQL Server ne rend normalement le contrôle à l'appelant qu'après avoir pu garantir que la transaction est durable. En d'autres termes, une fois que l'appelant a repris le contrôle après avoir validé une transaction, il peut être sûr que même si un instant plus tard, le serveur subit une panne de courant, les modifications de la transaction sont apportées à la base de données. Tant que le serveur redémarre avec succès et que les fichiers de la base de données ne sont pas corrompus, vous constaterez que toutes les modifications de transaction ont été appliquées.
La façon dont SQL Server applique la durabilité des transactions, en partie, consiste à s'assurer que toutes les modifications de la transaction sont écrites dans le journal des transactions de la base de données sur le disque avant de rendre le contrôle à l'appelant. En cas de panne de courant après l'accusé de réception de la validation d'une transaction, vous savez que toutes ces modifications ont au moins été écrites dans le journal des transactions sur disque. C'est le cas même si les pages de données associées ont été modifiées uniquement dans le cache de données (le pool de mémoire tampon) mais pas encore vidées dans les fichiers de données sur le disque. Lorsque vous redémarrez SQL Server, pendant la phase de rétablissement du processus de récupération, SQL Server utilise les informations enregistrées dans le journal pour relire les modifications qui ont été appliquées après le dernier point de contrôle et qui n'ont pas été apportées aux fichiers de données. Il y a un peu plus dans l'histoire selon le modèle de récupération que vous utilisez et si des opérations en bloc ont été appliquées après le dernier point de contrôle, mais pour les besoins de notre discussion, il suffit de se concentrer sur la partie qui implique de durcir les modifications apportées au journal des transactions.
La partie délicate de l'architecture de journalisation de SQL Server est que les écritures de journal sont séquentielles. Si SQL Server n'avait pas utilisé une sorte de tampon de journal pour atténuer les écritures de journal sur le disque, les systèmes gourmands en écriture, en particulier ceux qui impliquent de nombreuses petites transactions, se heurteraient rapidement à de terribles goulots d'étranglement liés aux écritures de journal.
Pour atténuer l'impact négatif sur les performances des écritures de journal séquentielles fréquentes sur le disque, SQL Server utilise un tampon de journal en mémoire. Les écritures de journal sont d'abord effectuées dans le tampon de journal et certaines conditions obligent SQL Server à vider ou à renforcer le tampon de journal sur le disque. L'unité renforcée (alias bloc de journal) peut aller d'un minimum d'une taille de secteur (512 octets) à un maximum de 60 Ko. Voici les conditions qui déclenchent un vidage de la mémoire tampon du journal (ignorez les parties qui apparaissent entre crochets pour l'instant) :
- SQL Server reçoit une demande de validation d'une transaction [entièrement durable] qui modifie les données [dans une base de données autre que tempdb]
- La mémoire tampon du journal se remplit, atteignant sa capacité de 60 Ko
- SQL Server doit renforcer les pages de données modifiées, par exemple lors d'un processus de point de contrôle, et les enregistrements de journal représentant les modifications apportées à ces pages n'ont pas encore été renforcés (journalisation en écriture anticipée , ou WAL en abrégé)
- Vous demandez manuellement un vidage du tampon du journal en exécutant la procédure sys.sp_flush_log
- SQL Server écrit une nouvelle valeur de récupération liée au cache de séquence [dans une base de données autre que tempdb]
Les quatre premières conditions devraient être assez claires, si vous ignorez pour l'instant les informations entre crochets. Le dernier n'est peut-être pas encore clair, mais je l'expliquerai en détail plus tard dans l'article.
Le temps pendant lequel SQL Server attend qu'une opération d'E/S gérant un vidage de tampon de journal se termine est reflété par le type d'attente WRITELOG.
Alors, pourquoi ces informations sont-elles si intéressantes et qu'en faisons-nous ? Comprendre les conditions qui déclenchent les vidages de la mémoire tampon du journal peut vous aider à comprendre pourquoi certaines charges de travail connaissent des goulots d'étranglement associés. En outre, dans certains cas, vous pouvez prendre des mesures pour réduire ou éliminer ces goulots d'étranglement. Je couvrirai un certain nombre d'exemples comme une grande transaction par rapport à de nombreuses petites transactions, des transactions entièrement durables par rapport à des transactions durables retardées, une base de données utilisateur par rapport à tempdb et la mise en cache d'objets de séquence.
Une grosse transaction contre plusieurs petites transactions
Comme mentionné, l'une des conditions qui déclenche un vidage de la mémoire tampon du journal est lorsque vous validez une transaction pour garantir la durabilité de la transaction. Cela signifie que les charges de travail qui impliquent de nombreuses petites transactions, comme les charges de travail OLTP, peuvent potentiellement rencontrer des goulots d'étranglement liés à l'écriture de journaux.
Même si ce n'est souvent pas le cas, si vous avez une seule session soumettant de nombreux petits changements, un moyen simple et efficace d'optimiser le travail consiste à appliquer les changements dans une seule grosse transaction au lieu de plusieurs petites.
Considérez l'exemple simplifié suivant (téléchargez PerformanceV3 ici) :
SET NOCOUNT ON ; UTILISEZ PerformanceV3 ; ALTER DATABASE PerformanceV3 SET DELAYED_DURABILITY =Désactivé ; -- par défaut SUPPRIMER LA TABLE SI EXISTE dbo.T1 ; CREATE TABLE dbo.T1(col1 INT NOT NULL); DÉCLARER @i COMME INT =1 ; WHILE @i <=1000000BEGIN BEGIN TRAN INSERT INTO dbo.T1(col1) VALUES(@i); COMMIT TRAN ; SET @i +=1;FIN;
Ce code exécute 1 000 000 de petites transactions qui modifient les données dans une base de données utilisateur. Ce travail déclenchera au moins 1 000 000 vidages de la mémoire tampon du journal. Vous pourriez en obtenir quelques-uns supplémentaires en raison du remplissage du tampon de journal. Vous pouvez utiliser le modèle de test suivant pour compter le nombre de vidages de la mémoire tampon du journal et mesurer le temps nécessaire à l'exécution du travail :
-- Modèle de test -- ... La préparation va ici ... -- Compter les vidages de journal et mesurer le tempsDECLARE @logflushes AS INT, @starttime AS DATETIME2, @duration AS INT ; -- Stats beforeSET @logflushes =( SELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name ='Log Flushes/sec' AND instance_name =@db ); SET @starttime =SYSDATETIME(); -- ... Le travail réel va ici ... -- Stats afterSET @duration =DATEDIFF(second, @starttime, SYSDATETIME());SET @logflushes =( SELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name ='Log Flushes/sec ' ET nom_instance =@db ) - @logflushes; SELECT @duration AS durationinseconds, @logflushes AS logflushes ;
Même si le nom du compteur de performances est Log Flushes/sec, il continue en fait à accumuler le nombre de vidages de la mémoire tampon du journal jusqu'à présent. Ainsi, le code soustrait le nombre avant le travail du nombre après le travail pour déterminer le nombre de vidages de journal générés par le travail. Ce code mesure également le temps en secondes qu'il a fallu pour terminer le travail. Même si je ne le fais pas ici, vous pouvez, si vous le souhaitez, déterminer de la même manière le nombre d'enregistrements de journal et la taille écrite dans le journal par le travail en interrogeant les états avant et après le travail du fn_dblog fonction.
Pour notre exemple ci-dessus, voici la partie que vous devez placer dans la section de préparation du modèle de test :
-- PreparationSET NOCOUNT ON;USE PerformanceV3; ALTER DATABASE PerformanceV3 SET DELAYED_DURABILITY =Désactivé ; SUPPRIMER TABLE SI EXISTE dbo.T1 ; CREATE TABLE dbo.T1(col1 INT NOT NULL); DECLARE @db AS sysname =N'PerformanceV3'; DECLARE @logflushes AS INT, @starttime AS DATETIME2, @duration AS INT ;
Et voici la partie que vous devez placer dans la section de travail réel :
-- Travail réelDECLARE @i AS INT =1 ; WHILE @i <=1000000BEGIN BEGIN TRAN INSERT INTO dbo.T1(col1) VALUES(@i); COMMIT TRAN ; SET @i +=1;FIN;
Au total, vous obtenez le code suivant :
-- Exemple de test avec de nombreuses petites transactions entièrement durables dans la base de données utilisateur-- ... La préparation va ici ... -- PreparationSET NOCOUNT ON;USE PerformanceV3; ALTER DATABASE PerformanceV3 SET DELAYED_DURABILITY =Désactivé ; SUPPRIMER TABLE SI EXISTE dbo.T1 ; CREATE TABLE dbo.T1(col1 INT NOT NULL); DECLARE @db AS sysname =N'PerformanceV3'; DECLARE @logflushes AS INT, @starttime AS DATETIME2, @duration AS INT ; -- Stats beforeSET @logflushes =( SELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name ='Log Flushes/sec' AND instance_name =@db ); SET @starttime =SYSDATETIME(); -- ... Le travail réel va ici ... -- Travail réelDECLARE @i AS INT =1; WHILE @i <=1000000BEGIN BEGIN TRAN INSERT INTO dbo.T1(col1) VALUES(@i); COMMIT TRAN ; SET @i +=1;FIN; -- Stats afterSET @duration =DATEDIFF(seconde, @starttime, SYSDATETIME()); SET @logflushes =( SELECT cntr_value FROM sys.dm_os_performance_counters WHERE counter_name ='Log Flushes/sec' AND instance_name =@db ) - @logflushes; SELECT @duration AS durationinseconds, @logflushes AS logflushes ;
Ce code a pris 193 secondes pour se terminer sur mon système et a déclenché 1 000 036 vidages de tampon de journal. C'est très lent, mais cela peut s'expliquer par le grand nombre de vidages de journaux.
Dans les charges de travail OLTP typiques, différentes sessions soumettent simultanément de petites modifications dans différentes petites transactions, ce n'est donc pas comme si vous aviez vraiment la possibilité d'encapsuler de nombreuses petites modifications dans une seule grande transaction. Cependant, si votre situation est que toutes les petites modifications sont soumises à partir de la même session, un moyen simple d'optimiser le travail consiste à l'encapsuler dans une seule transaction. Cela vous procurera deux avantages principaux. La première est que votre travail écrira moins d'enregistrements de journal. Avec 1 000 000 de petites transactions, chaque transaction écrit en fait trois enregistrements de journal :un pour commencer la transaction, un pour la modification et un pour valider la transaction. Donc, vous regardez environ 3 000 000 d'enregistrements de journaux de transactions contre un peu plus de 1 000 000 lorsqu'ils sont exécutés comme une seule grosse transaction. Mais plus important encore, avec une grosse transaction, la plupart des vidages de journal ne sont déclenchés que lorsque le tampon de journal se remplit, plus un vidage de journal supplémentaire à la toute fin de la transaction lorsqu'elle est validée. La différence de performances peut être assez importante. Pour tester le travail dans une grosse transaction, utilisez le code suivant dans la partie travail réel du modèle de test :
-- Travail réelBEGIN TRAN ; DÉCLARER @i COMME INT =1 ; TANT QUE @i <=1000000BEGIN INSERT INTO dbo.T1(col1) VALUES(@i); SET @i +=1 ; FINIR; COMMIT TRAN ;
Sur mon système, ce travail s'est terminé en 7 secondes et a déclenché 1 758 vidages de journal. Voici une comparaison entre les deux options :
#transactions log flushes durée en secondes---------------------- ------------ -------------- ------1000000 1000036 1931 1758 7
Mais encore une fois, dans les charges de travail OLTP typiques, vous n'avez pas vraiment la possibilité de remplacer de nombreuses petites transactions soumises à partir de différentes sessions par une seule grosse transaction soumise à partir de la même session.
Transactions entièrement durables contre transactions durables retardées
À partir de SQL Server 2014, vous pouvez utiliser une fonctionnalité appelée durabilité retardée qui vous permet d'améliorer les performances des charges de travail avec de nombreuses petites transactions, même si elles sont soumises par différentes sessions, en sacrifiant la garantie de durabilité complète normale. Lors de la validation d'une transaction durable retardée, SQL Server accuse réception de la validation dès que l'enregistrement du journal de validation est écrit dans le tampon du journal, sans déclencher de vidage du tampon du journal. Le tampon du journal est vidé en raison de l'une des autres conditions susmentionnées, comme lorsqu'il se remplit, mais pas lorsqu'une transaction durable retardée est validée.
Avant d'utiliser cette fonctionnalité, vous devez réfléchir très attentivement si elle vous convient. En termes de performances, son impact n'est significatif que dans les charges de travail comportant de nombreuses petites transactions. Si, pour commencer, votre charge de travail implique principalement des transactions volumineuses, vous ne constaterez probablement aucun avantage en termes de performances. Plus important encore, vous devez réaliser le potentiel de perte de données. Supposons que l'application valide une transaction durable retardée. Un enregistrement de validation est écrit dans le tampon du journal et immédiatement acquitté (le contrôle est rendu à l'appelant). Si SQL Server subit une panne de courant avant que le tampon du journal ne soit vidé, après le redémarrage, le processus de récupération annule toutes les modifications apportées par la transaction, même si l'application pense qu'elle a été validée.
Alors, quand est-il acceptable d'utiliser cette fonctionnalité ? Un cas évident est lorsque la perte de données n'est pas un problème, comme cet exemple de Melissa Connors de SentryOne. Une autre est lorsqu'après un redémarrage, vous avez les moyens d'identifier les modifications qui n'ont pas été apportées à la base de données et que vous êtes en mesure de les reproduire. Si votre situation n'entre pas dans l'une de ces deux catégories, n'utilisez pas cette fonctionnalité malgré la tentation.
Pour travailler avec des transactions durables différées, vous devez définir une option de base de données appelée DELAYED_DURABILITY. Cette option peut être définie sur l'une des trois valeurs :
- Désactivé (par défaut) :toutes les transactions dans la base de données sont entièrement durables, et donc chaque validation déclenche un vidage du tampon de journal
- Forcé :toutes les transactions dans la base de données sont retardées de manière durable et, par conséquent, les validations ne déclenchent pas de vidage du tampon de journal
- Autorisé :sauf mention contraire, les transactions sont entièrement durables et leur validation déclenche un vidage du tampon de journal ; cependant, si vous utilisez l'option DELAYED_DURABILITY =ON dans une instruction COMMIT TRAN ou un bloc atomique (d'un proc compilé en mode natif), cette transaction particulière est retardée de manière durable et, par conséquent, sa validation ne déclenche pas de vidage du tampon de journal
À titre de test, utilisez le code suivant dans la section de préparation de notre modèle de test (notez que l'option de base de données est définie sur Forcé) :
-- PreparationSET NOCOUNT ON;USE PerformanceV3; -- http://tsql.solidq.com/SampleDatabases/PerformanceV3.zip ALTER DATABASE PerformanceV3 SET DELAYED_DURABILITY =Forcé ; SUPPRIMER TABLE SI EXISTE dbo.T1 ; CREATE TABLE dbo.T1(col1 INT NOT NULL); DECLARE @db AS sysname =N'PerformanceV3';
Et utilisez le code suivant dans la section travail réel (notice, 1 000 000 petites transactions) :
-- Travail réelDECLARE @i AS INT =1 ; WHILE @i <=1000000BEGIN BEGIN TRAN INSERT INTO dbo.T1(col1) VALUES(@i); COMMIT TRAN ; SET @i +=1;FIN;
Vous pouvez également utiliser le mode Autorisé au niveau de la base de données, puis dans la commande COMMIT TRAN, ajouter WITH (DELAYED_DURABILITY =ON).
Sur mon système, le travail a duré 22 secondes et déclenché 95 407 vidages de journal. C'est plus long que d'exécuter le travail comme une seule grosse transaction (7 secondes) puisque plus d'enregistrements de journal sont générés (rappelez-vous, par transaction, un pour commencer la transaction, un pour le changement et un pour valider la transaction) ; cependant, c'est beaucoup plus rapide que les 193 secondes qu'il a fallu pour terminer le travail en utilisant 1 000 000 de transactions entièrement durables puisque le nombre de vidages de journaux est passé de plus de 1 000 000 à moins de 100 000. De plus, avec une durabilité retardée, vous obtiendrez un gain de performances même si les transactions sont soumises à partir de différentes sessions où il n'est pas possible d'utiliser une seule grosse transaction.
Pour démontrer qu'il n'y a aucun avantage à utiliser la durabilité différée lorsque vous effectuez le travail en tant que transactions volumineuses, conservez le même code dans la partie préparation du dernier test et utilisez le code suivant dans la partie travail réelle :
-- Travail réelBEGIN TRAN ; DÉCLARER @i COMME INT =1 ; TANT QUE @i <=1000000BEGIN INSERT INTO dbo.T1(col1) VALUES(@i); SET @i +=1;FIN; COMMIT TRAN ;
J'ai obtenu 8 secondes d'exécution (contre 7 pour une grosse transaction entièrement durable) et 1 759 vidages de journal (contre 1 758). Les chiffres sont essentiellement les mêmes, mais avec la transaction durable retardée, vous courez le risque de perdre des données.
Voici un résumé des chiffres de performance pour les quatre tests :
durability #transactions log flushes durée en secondes--------------- -------------- ------ ------ --------------------plein 1000000 1000036 193plein 1 1758 7retardé 1000000 95407 22retardé 1 1759 8
Mémoire de classe de stockage
La fonctionnalité de durabilité retardée peut améliorer considérablement les performances des charges de travail de type OLTP qui impliquent un grand nombre de petites transactions de mise à jour nécessitant une fréquence élevée et une faible latence. Le problème est que vous risquez de perdre des données. Que se passe-t-il si vous ne pouvez autoriser aucune perte de données, mais que vous souhaitez toujours des gains de performances similaires à une durabilité retardée, où le tampon de journal n'est pas vidé à chaque validation, mais plutôt lorsqu'il se remplit ? Nous aimons tous manger le gâteau et l'avoir aussi, n'est-ce pas ?
Vous pouvez y parvenir dans SQL Server 2016 SP1 ou version ultérieure en utilisant une mémoire de classe de stockage, également appelée stockage non volatile NVDIMM-N. Ce matériel est essentiellement un module de mémoire vous offrant des performances de qualité mémoire, mais les informations y sont conservées et ne sont donc pas perdues lorsque l'alimentation est coupée. L'ajout dans SQL Server 2016 SP1 vous permet de configurer le tampon de journal en tant que tampon persistant sur ce matériel. Pour ce faire, configurez le SCM en tant que volume dans Windows et formatez-le en tant que volume DAX (Direct Access Mode). Vous ajoutez ensuite un fichier journal à la base de données à l'aide de la commande normale ALTER DATABASE
Pour plus de détails sur cette fonctionnalité, y compris les performances, consultez Accélération de la latence de validation des transactions à l'aide de la mémoire de classe de stockage dans Windows Server 2016/SQL Server 2016 SP1 par Kevin Farlee.
Curieusement, SQL Server 2019 améliore la prise en charge de la mémoire de classe de stockage au-delà du seul scénario de cache de journal persistant. Il prend en charge le placement de fichiers de données, de fichiers journaux et de fichiers de point de contrôle OLTP en mémoire sur ce matériel. Tout ce que vous avez à faire est de l'exposer en tant que volume au niveau du système d'exploitation et de le formater en tant que lecteur DAX. SQL Server 2019 reconnaît automatiquement cette technologie et fonctionne de manière éclairée mode, accédant directement à l'appareil, en contournant la pile de stockage du système d'exploitation. Bienvenue dans le futur !
Base de données utilisateur contre tempdb
La base de données tempdb est bien sûr créée à partir de zéro en tant que nouvelle copie de la base de données model chaque fois que vous redémarrez SQL Server. En tant que tel, il n'est jamais nécessaire de récupérer les données que vous écrivez dans tempdb, que vous les écriviez dans des tables temporaires, des variables de table ou des tables utilisateur. Tout est parti après le redémarrage. Sachant cela, SQL Server peut assouplir une grande partie des exigences liées à la journalisation. Par exemple, que vous activiez ou non l'option de durabilité différée, les événements de validation ne déclenchent pas de vidage de la mémoire tampon du journal. De plus, la quantité d'informations devant être consignées est réduite puisque SQL Server n'a besoin que de suffisamment d'informations pour prendre en charge l'annulation des transactions ou l'annulation du travail, si nécessaire, mais pas l'avancement des transactions ou la reprise du travail. Par conséquent, les enregistrements du journal des transactions représentant les modifications apportées à un objet dans tempdb ont tendance à être plus petits que lorsque la même modification est appliquée à un objet dans une base de données utilisateur.
Pour le démontrer, vous exécuterez les mêmes tests que vous avez exécutés précédemment dans PerformanceV3, mais cette fois dans tempdb. Nous commencerons par le test de nombreuses petites transactions lorsque l'option de base de données DELAYED_DURABILITY est définie sur Disabled (par défaut). Utilisez le code suivant dans la section de préparation du modèle de test :
-- PréparationSET NOCOUNT ON;USE tempdb; ALTER DATABASE tempdb SET DELAYED_DURABILITY =Désactivé ; SUPPRIMER TABLE SI EXISTE dbo.T1 ; CREATE TABLE dbo.T1(col1 INT NOT NULL); DECLARE @db AS sysname =N'tempdb';
Utilisez le code suivant dans la section travail réel :
-- Travail réelDECLARE @i AS INT =1 ; WHILE @i <=1000000BEGIN BEGIN TRAN INSERT INTO dbo.T1(col1) VALUES(@i); COMMIT TRAN ; SET @i +=1;FIN;
Ce travail a généré 5 095 vidages de journaux et a pris 19 secondes. Cela se compare à plus d'un million de vidages de journaux et 193 secondes dans une base de données d'utilisateurs avec une durabilité totale. C'est encore mieux qu'avec une durabilité retardée dans une base de données utilisateur (95 407 vidages de journal et 22 secondes) en raison de la taille réduite des enregistrements de journal.
Pour tester une grande transaction, laissez la section de préparation inchangée et utilisez le code suivant dans la section de travail réel :
-- Travail réelBEGIN TRAN ; DÉCLARER @i COMME INT =1 ; TANT QUE @i <=1000000BEGIN INSERT INTO dbo.T1(col1) VALUES(@i); SET @i +=1;FIN; COMMIT TRAN ;
J'ai eu 1 228 vidages de journal et 9 secondes d'exécution. Cela se compare à 1 758 vidages de journaux et à 7 secondes d'exécution dans la base de données utilisateur. Le temps d'exécution est similaire, voire un peu plus rapide dans la base de données utilisateur, mais il peut y avoir de petites variations entre les tests. La taille des enregistrements de journal dans tempdb est réduite et, par conséquent, vous obtenez moins de vidages de journal par rapport à la base de données utilisateur.
Vous pouvez également essayer d'exécuter les tests avec l'option DELAYED_DURABILITY définie sur Forced, mais cela n'aura aucun impact sur tempdb puisque, comme mentionné, de toute façon, les événements de validation ne déclenchent pas de vidage du journal dans tempdb.
Voici les mesures de performances pour tous les tests, à la fois dans la base de données utilisateur et dans tempdb :
durabilité de la base de données #transactions log flushes durée en secondes---------------------- ------------------- ----- --------- ------------ --------------------PerformanceV3 complète 1000000 1000036 193PerformanceV3 complète 1 1758 7PerformanceV3 retardé 1000000 95407 22PerformanceV3 retardé 1 1759 8tempdb complet 1000000 5095 19tempdb complet 1 1228 9tempdb retardé 1000000 5091 18tempdb retardé 1 1226 9
Mise en cache de l'objet de séquence
Peut-être qu'un cas surprenant qui déclenche des vidages de tampon de journal est lié à l'option de cache d'objet de séquence. Prenons comme exemple la définition de séquence suivante :
CRÉER SÉQUENCE dbo.Seq1 AS BIGINT MINVALUE 1 CACHE 50 ; -- la taille du cache par défaut est de 50 ;
Chaque fois que vous avez besoin d'une nouvelle valeur de séquence, vous utilisez la fonction NEXT VALUE FOR, comme ceci :
SÉLECTIONNER LA VALEUR SUIVANTE POUR dbo.Seq1 ;
La propriété CACHE est une fonction de performance. Sans cela, chaque fois qu'une nouvelle valeur de séquence était demandée, SQL Server aurait dû écrire la valeur actuelle sur le disque à des fins de récupération. En effet, c'est le comportement que vous obtenez lorsque vous utilisez le mode NO CACHE. Au lieu de cela, lorsque l'option est définie sur une valeur supérieure à zéro, SQL Server écrit une valeur de récupération sur le disque une seule fois pour chaque nombre de demandes de taille de cache. SQL Server conserve deux membres en mémoire, dimensionnés en tant que type de séquence, l'un contenant la valeur actuelle et l'autre contenant le nombre de valeurs restantes avant que la prochaine écriture sur disque de la valeur de récupération ne soit nécessaire. En cas de panne de courant, au redémarrage, SQL Server définit la valeur de séquence actuelle sur la valeur de récupération.
C'est probablement beaucoup plus facile à expliquer avec un exemple. Considérez la définition de séquence ci-dessus avec l'option CACHE définie sur 50 (par défaut). Vous demandez une nouvelle valeur de séquence pour la première fois en exécutant l'instruction SELECT ci-dessus. SQL Server définit les membres susmentionnés sur les valeurs suivantes :
Valeur de récupération sur disque :50, valeur actuelle en mémoire :1, valeurs en mémoire restantes :49, vous obtenez :1
49 requêtes supplémentaires ne vont pas toucher le disque, mais uniquement mettre à jour les membres de la mémoire. Après 50 requêtes au total, les membres sont définis sur les valeurs suivantes :
Valeur de récupération sur disque :50, valeur actuelle en mémoire :50, valeurs en mémoire restantes :0, vous obtenez :50
Faites une autre demande pour une nouvelle valeur de séquence, et cela déclenche une écriture sur disque de la valeur de récupération 100. Les membres sont alors définis sur les valeurs suivantes :
Valeur de récupération sur disque :100, valeur actuelle en mémoire :51, valeurs en mémoire restantes :49, vous obtenez :51
Si, à ce stade, le système subit une panne de courant, après le redémarrage, la valeur de séquence actuelle est définie sur 100 (la valeur récupérée à partir du disque). La requête suivante pour une valeur de séquence produit 101 (écriture de la valeur de récupération 150 sur le disque). Vous avez perdu toutes les valeurs comprises entre 52 et 100. Le maximum que vous pouvez perdre en raison d'une fin incorrecte du processus SQL Server, comme dans le cas d'une panne de courant, est d'autant de valeurs que la taille du cache. Le compromis est clair; plus la taille du cache est grande, moins le disque écrit de la valeur de récupération, et donc meilleures sont les performances. En même temps, plus l'écart qui peut être généré entre deux valeurs de séquence en cas de panne de courant est grand.
Tout cela est assez simple et vous savez peut-être très bien comment cela fonctionne. Ce qui peut être surprenant, c'est que chaque fois que SQL Server écrit une nouvelle valeur de récupération sur le disque (toutes les 50 requêtes dans notre exemple), il durcit également le tampon du journal. Ce n'est pas le cas avec la propriété de colonne d'identité, même si SQL Server utilise en interne la même fonctionnalité de mise en cache pour l'identité que pour l'objet de séquence, cela ne vous permet tout simplement pas de contrôler sa taille. Il est activé par défaut avec la taille 10000 pour BIGINT et NUMERIC, 1000 pour INT, 100 pour SMALLINT et 10 POUR TINYINT. Si vous le souhaitez, vous pouvez le désactiver avec l'indicateur de trace 272 ou l'option de configuration étendue IDENTITY_CACHE (2017+). La raison pour laquelle SQL Server n'a pas besoin de vider la mémoire tampon du journal lors de l'écriture sur le disque de la valeur de récupération liée au cache d'identité est qu'une nouvelle valeur d'identité ne peut être créée que lors de l'insertion d'une ligne dans une table. En cas de panne de courant, une ligne insérée dans une table par une transaction qui n'a pas été validée sera extraite de la table dans le cadre du processus de récupération de la base de données au redémarrage du système. Ainsi, même si après le redémarrage, SQL Server génère la même valeur d'identité que celle créée dans la transaction qui n'a pas été validée, il n'y a aucune chance pour les doublons puisque la ligne a été extraite de la table. Si la transaction avait été validée, cela aurait déclenché un vidage du journal, ce qui aurait également persisté dans l'écriture d'une valeur de récupération liée au cache. Par conséquent, Microsoft ne s'est pas senti obligé de vider le tampon du journal chaque fois qu'une écriture sur disque liée au cache d'identité de la valeur de récupération a lieu.
Avec l'objet séquence, la situation est différente. Une application peut demander une nouvelle valeur de séquence et ne pas la stocker dans la base de données. En cas de panne de courant après la création d'une nouvelle valeur de séquence dans une transaction qui n'a pas été validée, après le redémarrage, SQL Server n'a aucun moyen de dire à l'application de ne pas s'appuyer sur cette valeur. Par conséquent, pour éviter de créer une nouvelle valeur de séquence après le redémarrage qui soit égale à une valeur de séquence générée précédemment, SQL Server force un vidage du journal chaque fois qu'une nouvelle valeur de récupération liée au cache de séquence est écrite sur le disque. Une exception à cette règle est lorsque l'objet de séquence est créé dans tempdb, bien sûr, il n'y a pas besoin de tels vidages de journal puisque de toute façon après un redémarrage du système, tempdb est créé à nouveau.
Un impact négatif sur les performances des vidages fréquents du journal est particulièrement visible lors de l'utilisation d'une très petite taille de cache de séquence et dans une transaction générant beaucoup de valeur de séquence, par exemple lors de l'insertion de nombreuses lignes dans une table. Sans la séquence, une telle transaction renforcerait principalement le tampon du journal lorsqu'il se remplit, plus une fois de plus lorsque la transaction est validée. Mais avec la séquence, vous obtenez un vidage du journal chaque fois qu'une écriture sur disque d'une valeur de récupération a lieu. C'est pourquoi vous voulez éviter d'utiliser une petite taille de cache - sans parler du mode NO CACHE.
Pour le démontrer, utilisez le code suivant dans la section de préparation de notre modèle de test :
-- PreparationSET NOCOUNT ON;USE PerformanceV3; -- essayez PerformanceV3, tempdb ALTER DATABASE PerformanceV3 -- essayez PerformanceV3, tempdb SET DELAYED_DURABILITY =Disabled; -- essayez Désactivé, Forcé DROP TABLE IF EXISTS dbo.T1; SUPPRIMER LA SÉQUENCE SI EXISTE dbo.Seq1 ; CREATE SEQUENCE dbo.Seq1 AS BIGINT MINVALUE 1 CACHE 50 ; -- essayez NO CACHE, CACHE 50, CACHE 10000 DECLARE @db AS sysname =N'PerformanceV3'; -- essayez PerformanceV3, tempdb
Et le code suivant dans la section travail réel :
-- Actual workSELECT -- n -- pour tester sans seq NEXT VALUE FOR dbo.Seq1 AS n -- pour tester sequenceINTO dbo.T1FROM PerformanceV3.dbo.GetNums(1, 1000000) AS N;
Ce code utilise une transaction pour écrire 1 000 000 lignes dans une table à l'aide de l'instruction SELECT INTO, générant autant de valeurs de séquence que le nombre de lignes insérées.
Comme indiqué dans les commentaires, exécutez le test avec NO CACHE, CACHE 50 et CACHE 10000, à la fois dans PerformanceV3 et dans tempdb, et essayez à la fois des transactions entièrement durables et des transactions durables retardées.
Voici les chiffres de performance que j'ai obtenus sur mon système :
durée des vidages du journal du cache de durabilité de la base de données en secondes -------------- ------------------- ------ --- ------------ --------------------PerformanceV3 complet PAS DE CACHE 1000047 171PerformanceV3 complet 50 20008 4PerformanceV3 complet 10000 339 <1tempdb full NO CACHE 96 4tempdb full 50 74 1tempdb full 10000 8 <1PerformanceV3 retardé NO CACHE 1000045 166PerformanceV3 retardé 50 20011 4PerformanceV3 retardé 10000 334 <1tempdb retardé NO CACHE 91 4tempdb retardé 50 74 1tempdb retardé 10010Il y a pas mal de choses intéressantes à remarquer.
Avec NO CACHE, vous obtenez un vidage du journal pour chaque valeur de séquence générée. Par conséquent, il est fortement recommandé de l'éviter.
Avec une petite taille de cache de séquence, vous obtenez toujours de nombreux vidages de journaux. La situation n'est peut-être pas aussi mauvaise qu'avec NO CACHE, mais notez que la charge de travail a pris 4 secondes pour se terminer avec la taille de cache par défaut de 50, contre moins d'une seconde avec la taille de 10 000. Personnellement, j'utilise 10 000 comme valeur préférée.
In tempdb you don’t get log flushes when a sequence cache-related recovery value is written to disk, but the recovery value is still written to disk every cache-sized number of requests. That’s perhaps surprising since such a value would never need to be recovered. Therefore, even when using a sequence object in tempdb, I’d still recommend using a large cache size.
Also notice that delayed durability doesn’t prevent the need for log flushes every time the sequence cache-related recovery value is written to disk.
Conclusion
This article focused on log buffer flushes. Understanding this aspect of SQL Server’s logging architecture is important especially in order to be able to optimize OLTP-style workloads that require high frequency and low latency. Workloads using In-Memory OLTP included, of course. You have more options with newer features like delayed durability and persisted log buffer with storage class memory. Make sure you’re very careful with the former, though, since it does incur potential for data loss unlike the latter.
Be careful not to use the sequence object with a small cache size, not to speak of the NO CACHE mode. I find the default size 50 too small and prefer to use 10,000. I’ve heard people expressing concerns that with a cache size 10000, after multiple power failures they might lose all the values in the type. However, even with a four-byte INT type, using only the positive range, 10,000 fits 214,748 times. If your system experience that many power failures, you have a completely different problem to worry about. Therefore, I feel very comfortable with a cache size of 10,000.