Comment consigner les détails des erreurs lors de l'utilisation de try / catch pour les commandes de sauvegarde SQL dynamiques

10

Lors de l'émission d'une commande de sauvegarde dans une procédure stockée qui utilise un catch catch et un SQL dynamique, les messages d'erreur sont très généraux par rapport à l'exécution directe de la commande de sauvegarde.

Essayez / Catch dans SP:

    begin try
        execute sp_executesql @sql;  -- a backup command
    end try
    begin catch  
        print ERROR_MESSAGE();  -- save to log, etc.
    end catch

Résulte en

50000: usp_Backup: 117: BACKUP DATABASE se termine anormalement.

wheareas émettant la commande brute:

    backup DATABASE someDb to disk...

Des résultats plus détaillés:

Erreur de recherche - Erreur de base de données SQL Server: une erreur d'E / S non récupérable s'est produite sur le fichier "H: \ FolderName \ Filename.bak:" 112 (il n'y a pas assez d'espace sur le disque.).

Existe-t-il un moyen d'attraper ces détails dans des variables au sein de la procédure stockée (pour se connecter, renvoyer à l'appelant, pour la logique de nouvelle tentative)? Il semble que les détails arrivent sur le canal de message mais je voudrais qu'ils soient disponibles dans le SP.

crokusek
la source
Vous pouvez voir ceci: stackoverflow.com/questions/5966670/…
8kb

Réponses:

13

Lorsqu'il BACKUP DATABASEgénère une erreur, il en génère en fait deux. Malheureusement, TRY/CATCHn'est pas capable de capturer la première erreur; il ne capture que la deuxième erreur.

Je soupçonne que votre meilleur pari pour capturer la vraie raison derrière une sauvegarde échouée est d'automatiser vos sauvegardes via SQLCMD (avec -opour envoyer la sortie vers un fichier), SSIS, C #, PowerShell etc. Tout cela vous donnera un plus grand contrôle sur la capture de tous des erreurs.

La réponse SO dans le commentaire suggère d'utiliser DBCC OUTPUTBUFFER- bien que cela soit possible, cela ne semble pas du tout être un jeu d'enfant. N'hésitez pas à vous amuser avec cette procédure sur le site d'Erland Sommarskog , mais cela ne semble toujours pas bien fonctionner en combinaison avec TRY/CATCH.

La seule façon dont j'ai semblé être capable de capturer le message d'erreur spGET_LastErrorMessageest de savoir si l'erreur réelle est levée. Si vous l'enveloppez dans un, TRY/CATCHl'erreur est avalée et la procédure stockée ne fait rien:

BEGIN TRY
  EXEC sp_executesql N'backup that fails...';
END TRY
BEGIN CATCH
  EXEC dbo.spGet_LastErrorMessage;
END CATCH

Dans SQL Server <2012, vous ne pouvez pas relancer l'erreur vous-même, mais vous pouvez le faire dans SQL Server 2012 et versions ultérieures. Donc, ces deux variantes fonctionnent:

CREATE PROCEDURE dbo.dothebackup
AS
BEGIN
  SET NOCOUNT ON;
  EXEC sp_executesql N'backup that fails...';
END
GO

EXEC dbo.dothebackup;
EXEC dbo.spGET_LastErrorMessage;

Ou en 2012 et au-dessus, cela fonctionne, mais dans une large mesure, cela va à l'encontre du but de TRY/CATCH, car l'erreur d'origine est toujours levée:

CREATE PROCEDURE dbo.dothebackup2
AS
BEGIN
  SET NOCOUNT ON;
  BEGIN TRY
    EXEC sp_executesql N'backup that fails...';
  END TRY
  BEGIN CATCH
    THROW;
  END CATCH
END
GO

EXEC dbo.dothebackup2;
EXEC dbo.spGET_LastErrorMessage;

Dans ces deux cas, l'erreur est toujours renvoyée au client, bien sûr. Donc, si vous utilisez TRY/CATCHpour éviter cela, à moins qu'il n'y ait une faille à laquelle je ne pense pas, je crains que vous n'ayez à faire un choix ... soit donnez à l'utilisateur l'erreur et soyez en mesure de capturer des détails sur ou supprimez l'erreur et la raison réelle.

Aaron Bertrand
la source
Aussi ridicule qu'elle soit, l'approche Sommarskog ne semble pas hors de question si je voulais juste fournir un peu de contexte à l'appelant dans une interface. Mieux que de démarrer un processus séparé. Êtes-vous en train de dire que cela ne fonctionnera pas dans un TRY / CATCH?
crokusek
@crokusek J'ai essayé une variante et le résultat est venu vide. Je vais lui donner un autre coup aujourd'hui.
Aaron Bertrand
En cas d'absence d'erreur, LastErrorMessage () obtient-il les résultats de toute erreur précédente de la session? Ensuite, si les deux derniers exécutables sont exécutés en tant que script, le premier exécutable pourrait peut-être être encapsulé dans un try / catch et dans le catch, définir une variable, puis relancer. Ensuite, LastError est appelée uniquement si la variable est définie. Suppose que la relance ne saute pas le 2e appel qui, je pense, est généralement vrai dans un contexte de script. Je ne pourrai peut-être toujours pas utiliser cette approche à la fin, car elle ne peut pas être placée dans un SP si je comprends bien. Merci quand même!
crokusek
2

Eh bien, je sais que c'est un vieux fil de discussion, et je sais que je suis sur le point de proposer un hack alambiqué, mais juste au cas où cela pourrait aider quelqu'un, voici: Puisque ces erreurs de sauvegarde sont enregistrées, vous pouvez utiliser xp_readerrorlog dans la capture bloquer pour gratter le journal des messages associés (erreur ou info). Vous pouvez rechercher des paramètres xp_readerrorlog sur Google, mais en bref, vous pouvez spécifier une chaîne de recherche et un filtre de temps de début qui sont utiles dans ce cas. Je ne sais pas si cela aiderait votre logique de nouvelle tentative, mais pour capturer des informations ou des erreurs pour la journalisation, j'ai trouvé quelque chose comme ça ...

IF OBJECT_ID('tempdb.dbo.#Results') IS NOT NULL DROP TABLE #Results
CREATE TABLE #Results (LogDate datetime,ProcessInfo nvarchar(100),LogText nvarchar(4000))
BEGIN TRY
SELECT @begintime = GETDATE()
EXEC sp_executesql @SQL --your backup statement string
INSERT #Results
EXEC  xp_readerrorlog 0, 1, N'backed up',@databasename,@begintime
SELECT @result = LogText from #Results where ProcessInfo = 'Backup' order by logdate desc
END TRY
BEGIN CATCH
INSERT #Results
EXEC  xp_readerrorlog 0, 1, N'Backup',@databasename,@begintime
SELECT @result = LogText from #Results where ProcessInfo = 'spid'+cast(@@SPID as varchar(6)) order by logdate desc
END CATCH
PRINT @result

HTH

Andy McD
la source
Cela fonctionne très bien pour certaines erreurs courantes, mais certaines erreurs ne sont apparemment transmises directement qu'au client. Le journal sp_readerrorlog comprendra un message disant se référer au "journal des applications", où je suppose que par "application", ils signifient le processus externe qui émet des commandes. SO Link
crokusek
0

Vous pouvez enregistrer les détails de l'erreur dans une table. Vous pouvez également créer un fichier journal, mais cela peut nécessiter un CLR ou xp_cmdshell. Vous pouvez également envoyer des e-mails de base de données, mais cela peut entraîner des problèmes de spam et n'est pas un journal approprié.

Le tableau est le plus simple.

  1. Créer un tableau pour stocker les erreurs
  2. Créer une procédure stockée qui s'insère dans la table d'erreurs
  3. Appelez la procédure stockée dans le bloc catch

Regardez l'exemple de Jeremy Kadlec fourni dans le lien ci-dessous:

http://www.mssqltips.com/sqlservertip/1152/standardized-sql-server-error-handling-and-centralized-logging/

Brian
la source
3
Le problème n'est pas de savoir quoi faire avec les erreurs, c'est que le message d'erreur approprié n'est pas disponible pour certaines commandes CATCH. En effet, seul le dernier message d'erreur est renvoyé dans ERROR_MESSAGE()...
Aaron Bertrand