Erreur de serveur lié non détectée par TRY-CATCH

14

Je configure un travail pour parcourir une liste de serveurs liés et exécuter une requête spécifique sur chacun d'eux. J'essaie d'exécuter la requête à l'intérieur d'un bloc TRY-CATCH, donc s'il y a un problème avec un serveur particulier, je peux le journaliser mais continuer avec les autres serveurs.

La requête que j'exécute à l'intérieur de la boucle ressemble à ceci:

BEGIN TRY
    SELECT *
    FROM OPENQUERY([server1], 'SELECT 1 AS c;');
END TRY
BEGIN CATCH
    SELECT ERROR_NUMBER(), ERROR_MESSAGE();
END CATCH;

PRINT 'We got past the Catch block!';

En cas de problème de connexion au serveur, le code échoue immédiatement et n'est pas transféré vers le CATCHbloc. Si le serveur se connecte mais qu'il y a une erreur dans la requête réelle, par exemple diviser par zéro, cela est détecté comme prévu par le CATCHbloc.

Par exemple, j'ai créé un serveur lié avec un nom dont je sais qu'il n'existe pas. Lors de l'exécution de ce qui précède, je reçois simplement:

OLE DB provider "SQLNCLI" for linked server "nonserver" returned message 
    "Login timeout expired".
OLE DB provider "SQLNCLI" for linked server "nonserver" returned message 
    "An error has occurred while establishing a connection to the server. 
    When connecting to SQL Server 2005, this failure may be caused by the 
    fact that under the default settings SQL Server does not allow remote
    connections.".
Msg 53, Level 16, State 1, Line 0
Named Pipes Provider: Could not open a connection to SQL Server [53].

J'ai lu BOL sur TRY-CATCH et je sais qu'il ne détectera pas les erreurs de niveau 20+ qui interrompent la connexion, mais cela ne semble pas être le cas (ce n'est que le niveau 16).

Est-ce que quelqu'un sait pourquoi ces erreurs ne sont pas détectées correctement?

JamesLean
la source

Réponses:

11

Une chose que vous pouvez essayer est d'utiliser sp_testlinkedserver. Vous pouvez également lancer l' OPENQUERYutilisation de SQL dynamique (comme Max l'a correctement souligné), pour différer l'analyseur validant le nom du serveur jusqu'à l'exécution.

BEGIN TRY
    EXEC sp_testlinkedserver N'server1';

    EXEC sp_executesql N'SELECT * FROM OPENQUERY([server1], 
      ''SELECT 1 AS c;'');';
END TRY
BEGIN CATCH
    SELECT ERROR_NUMBER(), ERROR_MESSAGE();
END CATCH;

PRINT 'We got past the Catch block!';

Bien que cela fonctionne aussi bien sans sp_testlinkedserver, cette procédure peut toujours être utile pour vous empêcher d'essayer tout un tas de code contre ce serveur ...


De plus, comme en cas d' sp_testlinkedserveréchec, il échoue réellement au moment de la compilation, vous pouvez également différer cela et toujours le capturer en utilisant du SQL dynamique:

BEGIN TRY
  EXEC master.sys.sp_executesql N'EXEC sp_testlinkedserver N''server1'';';
  ...
END TRY
Aaron Bertrand
la source
6

Avez-vous essayé quelque chose comme ça?

BEGIN TRY
    DECLARE @cmd nvarchar(max);
    SET @cmd = 'SELECT * FROM OPENQUERY([server1], ''SELECT 1 AS c;'');';
    EXEC sp_executesql @cmd;
END TRY
BEGIN CATCH
    SELECT ERROR_NUMBER(), ERROR_MESSAGE();
END CATCH;

Selon les commentaires ci-dessous, cela fonctionne car l'erreur n'est plus générée au moment de la compilation. L'erreur se produit maintenant au moment de l'exécution, à l'intérieur de la procédure stockée sp_executesql.

Max Vernon
la source
Merci Max. Oui, j'avais pensé au SQL dynamique (et ce qui précède fonctionne en effet correctement). Je suis intéressé par POURQUOI l'erreur n'est pas détectée?
JamesLean
@AaronBertrand Si vous ajoutez un simple PRINT 'Start';tout en haut du script, cela est imprimé dans la sortie, même si la connexion échoue et que le script se termine avec l'erreur. Cela indiquerait donc une erreur d' exécution , n'est-ce pas? A moins que je ne le comprenne mal?
JamesLean
Gah, quand j'ai ajouté le, PRINTj'avais toujours l' sp_testlinkedserverappel dans le script. En fait, il n'est pas imprimé à l'aide de mon script d'origine (défaillant). Il semble donc que ce soit en fait une erreur au moment de la compilation , c'est pourquoi il n'est pas détecté.
JamesLean
@JamesLean trop drôle, quand je suis allé faire du repro pour confirmer ce que vous proposiez, j'avais commenté l' sp_testlinkedserverappel, mais j'ai laissé le SELECTSQL aussi dynamique. Le PRINTne se produit pas si vous faites directement référence au nom du serveur, de sorte que, comme je l'avais suggéré précédemment, il BEGIN TRYn'est jamais entré car l'erreur est déclenchée en premier.
Aaron Bertrand
4

Après enquête, il semble que cette erreur ne soit pas détectée car il s'agit d'une erreur de compilation plutôt que d'une erreur d'exécution. Pour illustrer cela, essayez ce qui suit:

PRINT 'Before TRY';

BEGIN TRY
    SELECT 1/0;

    SELECT *
    FROM OPENQUERY([nonserver], 'SELECT 1 AS c;');
END TRY
BEGIN CATCH
    SELECT ERROR_NUMBER(), ERROR_MESSAGE();
END CATCH;

L' PRINTinstruction initiale n'obtient pas de sortie, et l'erreur de division par zéro n'est pas exécutée / capturée. Le serveur inexistant provoque l'échec immédiat du script.

JamesLean
la source
3

J'ai récemment eu un problème similaire où j'ai appelé une procédure distante à partir d'un TRY-CATCH et la procédure a échoué en raison de la tentative d'insertion d'une clé en double (erreur d'exécution de niveau 16). Le bloc CATCH n'a pas été appelé. J'ai trouvé la raison dans cet article: https://technet.microsoft.com/en-us/library/ms191515(v=sql.105).aspx

La solution consiste à SET XACT_ABORT ON dans la procédure d'appel avant d'appeler la procédure distante. Lorsque XACT_ABORT est sur le bloc CATCH est appelé comme prévu. Vous devez savoir que le paramètre XACT_ABORT est propagé à la procédure distante, ce qui peut affecter son comportement.

RM Buda
la source
0
ALTER PROCEDURE dbo.LinkedServer_Status 
    @linked_server nvarchar(128),
    @exists bit OUT,
    @connected bit OUT,
    @server_datetime datetime OUT
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @server_id int;
    SELECT @server_id = server_id from sys.servers where name = @linked_server;
    IF (@@ROWCOUNT = 0)
        SELECT @exists = 0, @connected = 0, @server_datetime = null;
    ELSE BEGIN
        SELECT @exists = 1;
        BEGIN TRY
            DECLARE @TBL TABLE(server_datetime DateTime);
            DECLARE @SQL nVarChar(2048); -- MUST BE nVarChar
            SELECT @SQL =
                'SELECT server_datetime FROM OPENQUERY(['+RTRIM(@linked_server)+'], ''SELECT GETDATE() server_datetime'')'; 
            INSERT @TBL EXEC sp_executesql @SQL;
            SELECT TOP 1 @connected = 1, @server_datetime = server_datetime FROM @TBL;
        END TRY
        BEGIN CATCH
            SELECT @connected = 0, @server_datetime = null;
            SELECT ERROR_MESSAGE();
        END CATCH
    END;
END

-- now use stored procedure

SET NOCOUNT ON;

DECLARE
    @linked_server nvarchar(128),
    @exists bit,
    @connected bit,
    @server_datetime datetime

SELECT @linked_server = 'FRICKE BMS';

exec dbo.LinkedServer_Status
    @linked_server, 
    @exists OUT, 
    @connected OUT, 
    @server_datetime OUT;

IF (@exists = 0)
    PRINT 'Linked Server "' + @linked_server + '" DOES NOT Exist';
ELSE BEGIN
    PRINT 'Linked Server "' + @linked_server + '" Exists';
    IF (@connected = 0)
        PRINT 'Linked Server "' + @linked_server + '" NOT Connected';
    ELSE
        PRINT 'Linked Server "' + @linked_server + '" IS Connected; Server DateTime: '+convert(varchar(25), @server_datetime, 120) 
END;
Jeffrey W Lott
la source
1
Bonjour, tout d'abord bienvenue sur le site. Ici, nous aimons un peu d'explication sur le fonctionnement des choses plutôt qu'un mur de code sans informations supplémentaires. Mais merci pour la réponse.
Tom V - essayez topanswers.xyz