En quoi ces deux annulations de SQL Server sont-elles différentes?

13

Dans SQL Server 2008 R2, en quoi ces deux restaurations sont-elles différentes:

  1. Exécutez une ALTERinstruction, pendant quelques minutes, puis appuyez sur «Annuler l'exécution». La restauration complète prend quelques minutes.

  2. Exécutez la même ALTERinstruction, mais assurez-vous que le LDFfichier n'est pas assez volumineux pour qu'il se termine correctement. Une fois que la LDFlimite est atteinte et qu'aucune «croissance automatique» n'est autorisée, l'exécution de la requête s'arrête immédiatement (ou une restauration se produit) avec ce message d'erreur:

The statement has been terminated.
Msg 9002, Level 17, State 4, Line 1
The transaction log for database 'SampleDB' is full. 
To find out why space in the log cannot be reused, see the 
log_reuse_wait_desc column in sys.databases

En quoi ces deux sont-ils différents sur les points suivants?

  1. Pourquoi le deuxième «rollback» est-il instantané? Je ne suis pas tout à fait sûr si cela pourrait être appelé un retour en arrière. Je suppose que le journal des transactions est écrit au fur et à mesure que l'exécution progresse et une fois qu'il se rend compte qu'il n'y a pas assez d'espace pour terminer complètement la tâche, il s'arrête juste avec un message de «fin», sans validation.

  2. Que se passe-t-il lorsque le premier rollback prend autant de temps (un rollback à un seul thread)?
    2.1. SQL Server revient-il en arrière et annule-t-il les entrées effectuées dans le LDFfichier?
    2.2. La LDFtaille du fichier diminue à la fin de la restauration (à partir de DBCC SQLPERF(LOGSPACE))

  3. Une question supplémentaire: pendant le deuxième scénario, SQL Server commence à consommer LDF fichier assez rapidement. Dans mon cas, il est passé de 18% d'utilisation à 90% d'utilisation dans les premières minutes (<4 minutes). Mais une fois qu'il a atteint 99%, il y est resté pendant 8 minutes supplémentaires, tout en faisant fluctuer l'utilisation entre 99,1% et 99,8%. Il augmente (99,8%) et diminue (99,2%) et augmente à nouveau (99,7%) et diminue (99,5%) quelques fois avant que l'erreur ne soit levée. Que se passe-t-il dans les coulisses?

Tous les liens MSDN qui pourraient aider à expliquer cela davantage sont appréciés.

À la suggestion d'Ali Razeghi, j'ajoute perfmon: Disk Bytes/sec

Scénario 1:

Scénario 1

Scénario 2:

Scénario 2

ToC
la source
Juste un petit commentaire: taille du fichier! = Espace utilisé dans un fichier.
Aaron Bertrand
@Aaron Oui, je le connais. J'ai utilisé DBCC SQLPERF (LOGSPACE) pour mesurer l'utilisation. Le fichier LDF est resté le même pendant toute la durée, car j'ai limité la taille du fichier à 10 Go. L'utilisation interne varie.
ToC
1
Je soupçonne que le deuxième rollback apparaît instantané, car l'erreur est signalée une fois le rollback terminé. Ce sont probablement les 8 minutes que vous observez en 3., où l'utilisation LDF reste assez constante.
mustaccio
@mustaccio Je serais d'accord avec vous mais les artefacts pointent dans des directions différentes. Si en fait, la restauration s'est produite, alors l'utilisation du fichier journal doit être de retour à un nombre plus petit; et ne pas rester à 99,3% lorsque le message d'erreur est lancé.
ToC
1
Je pense que la restauration prend de l'espace de journal. Lorsque le journal est plein pendant la restauration, la base de données devient suspecte. Il n'est plus touché. Cela ressemble à une restauration instantanée, mais la restauration a été différée jusqu'à ce que vous puissiez reprendre la base de données en ligne (lorsqu'il y a de l'espace disque disponible). En outre, lorsque le journal est plein, SQL Server peut essayer de faire de la place en effectuant des points de contrôle, ce qui peut expliquer les pics d'activité IO.
usr

Réponses:

1

Comme indiqué ci-dessus, après avoir effectué plus de tests, je suis arrivé à des conclusions calculées. Je les ai tous résumés en un article de blog ici , mais je vais copier du contenu sur cet article pour la postérité.

Conjecture (basée sur certains tests)

Pour l'instant, je n'ai pas d'explication claire quant à la raison de cette situation. Mais voici mes estimations basées sur les artefacts recueillis lors des tests.

La restauration se produit dans les deux scénarios. L'un est un retour en arrière explicite (l'utilisateur clique sur le bouton Annuler), l'autre est implicite (le serveur SQL prend cette décision en interne).

Dans les deux scénarios, le trafic allant vers le fichier journal est cohérent. Voir les images ci-dessous:

Scénario 1:

Scénario 1:

Scénario 2:

Scénario 2

  • Un artefact qui a renforcé cette ligne de pensée est la capture de la trace SQL dans les deux scénarios.

    • Le scénario 1 va de soi, lorsque nous appuyons sur «Annuler», il revient en arrière.
    • Dans le scénario 2, le message d'erreur s'affiche après avoir effectué implicitement la «restauration». Dans Sql Trace, nous voyons le message d'erreur «Le journal des transactions pour la base de données« SampleDB »est plein» longtemps avant que le message ne s'affiche à l'écran. Donc, je suppose que les restaurations se produisent dans les deux scénarios, mais le message d'erreur est que le scénario 2 s'affiche après avoir effectué la restauration avec succès et complètement.
  • Le scénario 2 semble prendre plus de temps car il progresse beaucoup plus loin, de sorte que la restauration prend plus de temps.

Comportement inexpliqué:

  • Pourquoi l'utilisation du fichier journal varie-t-elle autant?
    • Il monte à 90%, puis à 85%, puis à 99% et y plane longtemps. Je le vois monter et descendre comme ça plusieurs fois: 99,2%, 99,8%, 99,1%, 99,7%. Pourquoi cela arrive-t-il?
    • Une explication possible est qu'il peut y avoir un processus d'arrière-plan (quelque chose comme Log Flush) qui nettoie le fichier journal toutes les quelques minutes. Et chaque fois qu'il entre en action, certaines entrées sont effacées, ce qui donne plus d'espace libre disponible.

Toutes les idées pour mieux expliquer ce comportement sont les bienvenues.

ToC
la source
2
Vérifié avec Paul Randal, il a confirmé que vous êtes arrivé à la bonne conclusion - le retour en arrière est le même dans les deux cas.
Paul White 9
0

J'ai essayé l'expérience suivante et obtenu des résultats similaires. Dans les deux cas, fn_dblog () montre que la restauration se produit et semble se produire plus rapidement dans le scénario 2 que dans le scénario 1.

Au fait, j'ai placé le MDF et le LDF sur le même disque externe (USB 2.0).

Ma conclusion initiale est qu'il n'y a pas de différence dans le fonctionnement de la restauration dans ce cas, et probablement toute différence de vitesse apparente est liée au sous-système d'E / S. Ce n'est que mon hypothèse de travail pour le moment.

Scénario 1:

  • Créez une base de données avec un fichier journal qui commence à 1 Mo, se développe en morceaux de 4 Mo et a une taille maximale de 100 Mo.
  • Ouvrez une transaction explicite, exécutez-la pendant 10 secondes, puis annulez-la manuellement dans SSMS
  • Regardez le nombre fn_dblog () et la taille de la réserve de journal et consultez DBCC SQLPERF (LOGSPACE)

Scénario 2:

  • Créez une base de données avec un fichier journal qui commence à 1 Mo, se développe en morceaux de 4 Mo et a une taille maximale de 100 Mo.
  • Ouvrez une transaction explicite, exécutez-la jusqu'à ce que le journal soit plein, une erreur apparaît
  • Regardez le nombre fn_dblog () et la taille de la réserve de journal et consultez DBCC SQLPERF (LOGSPACE)

Résultats du moniteur de performances:

Scénario 1: ***Scénario 1***

Scénario 2: *** Scénario 2 ***

Code:

UTILISER [maître];
ALLER

SI DATABASEPROPERTYEX (N'SampleDB ', N'Version')> 0
COMMENCER
    ALTER DATABASE [SampleDB] SET SINGLE_USER
        AVEC ROLLBACK IMMÉDIATE;
    DROP DATABASE [SampleDB];
FIN;
ALLER

CRÉER UNE BASE DE DONNÉES [SampleDB] SUR PRIMARY 
( 
      NAME = N'SampleDB '
    , FILENAME = N'E: \ data \ SampleDB.mdf ' 
    , TAILLE = 3MB 
    , FILEGROWTH = 1MB 
)
SE CONNECTER 
( 
      NAME = N'SampleDB_log '
    , FILENAME = N'E: \ data \ SampleDB_log.ldf '
    , SIZE = 1MB 
    , MAXSIZE = 100 Mo 
    , FILEGROWTH = 4MB 
);
ALLER

USE [SampleDB];
ALLER

- Ajouter une table
CRÉER LA TABLE dbo.test
(
    c1 CHAR (8000) NON NULL REPLICATE DEFAULT ('a', 8000)
) LE [PRIMAIRE];
ALLER

- Assurez-vous que nous ne sommes pas un modèle de récupération pseudo-simple
BASE DE DONNÉES DE SAUVEGARDE SampleDB
TO DISK = 'NUL';
ALLER

- Sauvegarder le fichier journal
JOURNAL DE SAUVEGARDE SampleDB
TO DISK = 'NUL';
ALLER

- Cochez l'espace de journal utilisé
DBCC SQLPERF (LOGSPACE);
ALLER

- Combien d'enregistrements sont visibles avec fn_dblog ()?
SELECT * FROM fn_dblog (NULL, NULL); - Environ 9 dans mon cas

/ **********************************
             SCÉNARIO 1
********************************** /
- Ouvrez une nouvelle transaction puis annulez-la
COMMENCER LA TRANSACTION

    INSÉRER DANS dbo.test VALEURS PAR DÉFAUT;
    GO 10000 - Let est exécuté pendant 10 secondes, puis appuyez sur Annuler dans la fenêtre de requête SSMS

    - Annuler la transaction
    - Cela devrait prendre quelques secondes pour terminer


- Pas besoin d'annuler la transaction, car l'annulation l'a déjà fait pour vous.
- Essayez-le. Vous obtiendrez cette erreur
- Msg 3903, niveau 16, état 1, ligne 1
- La demande ROLLBACK TRANSACTION n'a pas de BEGIN TRANSACTION correspondante.
TRANSACTION ROLLBACK;

- Quel est l'espace journal utilisé? Au-dessus de 100%.
DBCC SQLPERF (LOGSPACE);
ALLER

- Combien d'enregistrements sont visibles avec fn_dblog ()?
SELECT * 
FROM fn_dblog (NULL, NULL); - Environ 91 926 dans mon cas

- Réserve totale de journaux affichée par fn_dblog ()?
SELECT SUM ([Log Reserve]) AS [Total Log Reserve]
FROM fn_dblog (NULL, NULL); - Environ 88,72 Mo


/ **********************************
             SCÉNARIO 2
********************************** /
- Soufflez la DB et recommencez
UTILISER [maître];
ALLER

SI DATABASEPROPERTYEX (N'SampleDB ', N'Version')> 0
COMMENCER
    ALTER DATABASE [SampleDB] SET SINGLE_USER
        AVEC ROLLBACK IMMÉDIATE;
    DROP DATABASE [SampleDB];
FIN;
ALLER

CRÉER UNE BASE DE DONNÉES [SampleDB] SUR PRIMARY 
( 
      NAME = N'SampleDB '
    , FILENAME = N'E: \ data \ SampleDB.mdf ' 
    , TAILLE = 3MB 
    , FILEGROWTH = 1MB 
)
SE CONNECTER 
( 
      NAME = N'SampleDB_log '
    , FILENAME = N'E: \ data \ SampleDB_log.ldf '
    , SIZE = 1MB 
    , MAXSIZE = 100 Mo 
    , FILEGROWTH = 4MB 
);
ALLER

USE [SampleDB];
ALLER

- Ajouter une table
CRÉER LA TABLE dbo.test
(
    c1 CHAR (8000) NON NULL REPLICATE DEFAULT ('a', 8000)
) LE [PRIMAIRE];
ALLER

- Assurez-vous que nous ne sommes pas un modèle de récupération pseudo-simple
BASE DE DONNÉES DE SAUVEGARDE SampleDB
TO DISK = 'NUL';
ALLER

- Sauvegarder le fichier journal
JOURNAL DE SAUVEGARDE SampleDB
TO DISK = 'NUL';
ALLER

- Maintenant, explosons le fichier journal dans notre transaction
COMMENCER LA TRANSACTION
    INSÉRER DANS dbo.test VALEURS PAR DÉFAUT;
    GO 10000

- Le rollback ne se déclenche jamais. Essayez-le. Vous obtiendrez une erreur.
- Msg 3903, niveau 16, état 1, ligne 1
- La demande ROLLBACK TRANSACTION n'a pas de BEGIN TRANSACTION correspondante.
TRANSACTION ROLLBACK;

- Le fichier journal est-il plein à 100%? 
DBCC SQLPERF (LOGSPACE);

- Combien d'enregistrements sont visibles avec fn_dblog ()?
SELECT * 
FROM fn_dblog (NULL, NULL); - Environ 91 926 dans mon cas
ALLER

- Réserve totale de journaux affichée par fn_dblog ()?
SELECT SUM ([Log Reserve]) AS [Total Log Reserve]
FROM fn_dblog (NULL, NULL); - 88,72 Mo
ALLER
sans fil
la source
Merci pour les tests détaillés. Après avoir effectué plus de tests, je suis arrivé à une conclusion similaire. J'ai donc écrit un article de blog . Pour moi, le scénario 2 prend plus de temps à restaurer, car la quantité de travail effectuée dans le scénario 2, avant que Sql Server ne se rende compte que la restauration est plus importante que le scénario 1.
ToC