Errore del server collegato non rilevato da TRY-CATCH


14

Sto impostando un lavoro per scorrere un elenco di server collegati ed eseguire una query specifica su ciascuno di essi. Sto cercando di eseguire la query all'interno di un blocco TRY-CATCH, quindi se si verifica un problema con un determinato server posso registrarlo ma continuare con gli altri server.

La query che sto eseguendo all'interno del ciclo è simile a questa:

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!';

Se si verifica un problema di connessione al server, il codice non riesce immediatamente e non viene trasferito al CATCHblocco. Se il server si connette ma si verifica un errore nella query effettiva, ad esempio dividere per zero, questo viene rilevato come previsto dal CATCHblocco.

Ad esempio, ho creato un server collegato con un nome che so non esiste. Quando eseguo quanto sopra ottengo solo:

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].

Ho letto BOL TRY-CATCHe so che non colpirà gli errori di livello 20+ che interrompono la connessione, ma questo non sembra essere il caso (questo è solo il livello 16).

Qualcuno sa perché questi errori non vengono rilevati correttamente?

Risposte:


11

Una cosa che puoi provare è usare sp_testlinkedserver. È inoltre possibile emettere l' OPENQUERYutilizzo dell'SQL dinamico (come correttamente sottolineato da Max), per rinviare il parser che convalida il nome del server fino al runtime.

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!';

Mentre questo funziona ugualmente bene senza sp_testlinkedserver, quella procedura può ancora essere utile per impedirti di provare un sacco di codice contro quel server ...


Inoltre, poiché se sp_testlinkedserverfallisce in realtà fallisce al momento della compilazione, puoi rimandarlo e catturarlo utilizzando anche SQL dinamico lì:

BEGIN TRY
  EXEC master.sys.sp_executesql N'EXEC sp_testlinkedserver N''server1'';';
  ...
END TRY

6

Hai provato qualcosa del genere?

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;

Come da commenti di seguito, questo funziona poiché l'errore non viene più generato in fase di compilazione. L'errore si verifica ora in fase di esecuzione, all'interno della stored procedure sp_executesql.


Grazie Max. Sì, avevo pensato a SQL dinamico (e quanto sopra funziona davvero correttamente). Sono interessato a PERCHÉ l'errore non viene rilevato?
JamesLean

@AaronBertrand Se aggiungi un semplice PRINT 'Start';nella parte superiore dello script, questo viene stampato nell'output, anche se la connessione non riesce e lo script esce con l'errore. Quindi questo indicherebbe un errore di runtime , no? A meno che non lo fraintenda?
JamesLean

Gah, quando ho aggiunto che PRINTavevo ancora la sp_testlinkedserverchiamata nella sceneggiatura. In realtà, non viene stampato usando il mio script originale (non riuscito). Quindi sembra che questo sia in realtà un errore di compilazione , motivo per cui non viene catturato.
JamesLean

@JamesLean è troppo divertente, quando sono andato a riproporre per confermare ciò che stavi suggerendo, avevo commentato la sp_testlinkedserverchiamata, ma ho lasciato l' SELECTSQL dinamico. Il PRINTnon si verifica se si fa riferimento direttamente il nome del server, così come avevo suggerito in precedenza, BEGIN TRYnon è mai entrato perché l'errore è sollevato per primo.
Aaron Bertrand

4

Dopo l'indagine sembra che questo errore non venga rilevato poiché si tratta di un errore di compilazione piuttosto che di un errore di runtime. Per dimostrarlo, prova quanto segue:

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' PRINTistruzione iniziale non ottiene output, né la divisione per errore zero viene eseguita / catturata. Il server inesistente causa immediatamente un errore dello script.


3

Di recente ho avuto un problema simile in cui ho chiamato una procedura remota da un TRY-CATCH e la procedura non è riuscita a causa del tentativo di inserire una chiave duplicata (errore di runtime di livello 16). Il blocco CATCH non è stato invocato. Ho trovato il motivo in questo articolo: https://technet.microsoft.com/en-us/library/ms191515(v=sql.105).aspx

La soluzione è SET XACT_ABORT ON nella procedura di chiamata prima di richiamare la procedura remota. Quando XACT_ABORT è sul blocco CATCH viene richiamato come previsto. È necessario essere consapevoli che l'impostazione XACT_ABORT viene propagata alla procedura remota e ciò potrebbe influire sul suo comportamento.


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;

1
Ciao, innanzitutto benvenuto sul sito. Da queste parti ci piace un po 'di spiegazioni su come funzionano le cose piuttosto che un muro di codice senza ulteriori informazioni. Ma grazie per la risposta.
Tom V - prova topanswers.xyz il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.