Verbindungsserverfehler nicht von TRY-CATCH abgefangen

14

Ich richte einen Job ein, um eine Liste von Verbindungsservern zu durchlaufen und eine bestimmte Abfrage für jeden einzelnen auszuführen. Ich versuche, die Abfrage in einem TRY-CATCH-Block auszuführen. Wenn also ein Problem mit einem bestimmten Server vorliegt, kann ich es protokollieren und dann mit den anderen Servern fortfahren.

Die Abfrage, die ich in der Schleife ausführe, sieht ungefähr so ​​aus:

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

Wenn beim Herstellen der Verbindung zum Server ein Problem auftritt, schlägt der Code sofort fehl und wird nicht in den CATCHBlock übertragen. Wenn der Server eine Verbindung herstellt, die Abfrage jedoch fehlerhaft ist, z. B. durch Null dividieren, wird dies vom CATCHBlock erwartet abgefangen .

Ich habe beispielsweise einen Verbindungsserver mit einem Namen erstellt, von dem ich weiß, dass er nicht existiert. Beim Ausführen des oben genannten bekomme ich nur:

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

Ich habe BOL weiter gelesen TRY-CATCHund weiß, dass es keine Fehler ab Stufe 20 abfängt, die die Verbindung unterbrechen, aber dies scheint nicht der Fall zu sein (dies ist nur Stufe 16).

Weiß jemand, warum diese Fehler nicht richtig abgefangen werden?

JamesLean
quelle

Antworten:

11

Eine Sache, die Sie versuchen können, ist zu verwenden sp_testlinkedserver. Sie können auch die OPENQUERYVerwendung von dynamischem SQL (wie Max richtig ausgeführt hat) ausgeben , um den Parser zu verschieben, der den Servernamen bis zur Laufzeit überprüft.

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

Obwohl dies auch ohne funktioniert sp_testlinkedserver, kann dieses Verfahren dennoch hilfreich sein, um zu verhindern, dass Sie eine ganze Reihe von Code auf diesem Server testen ...


Wenn sp_testlinkedserverdies fehlschlägt, können Sie dies auch verschieben und dennoch mithilfe von dynamischem SQL erfassen:

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

Hast du so etwas probiert?

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;

Wie in den Kommentaren unten angegeben, funktioniert dies, da der Fehler zur Kompilierungszeit nicht mehr generiert wird. Der Fehler tritt jetzt zur Laufzeit in der gespeicherten Prozedur sp_executesql auf.

Max Vernon
quelle
Danke Max. Ja, ich hatte an dynamisches SQL gedacht (und das oben Genannte funktioniert tatsächlich richtig). Ich interessiere mich dafür, warum der Fehler nicht abgefangen wird.
JamesLean
@AaronBertrand Wenn Sie PRINT 'Start';ganz oben im Skript ein einfaches hinzufügen , wird dieses in der Ausgabe gedruckt, obwohl die Verbindung dann fehlschlägt und das Skript mit dem Fehler beendet wird. Das würde also auf einen Laufzeitfehler hinweisen, nicht wahr? Es sei denn, ich verstehe es falsch?
JamesLean
Gah, als ich das hinzufügte, hatte PRINTich noch den sp_testlinkedserverAnruf im Skript. Tatsächlich wird es nicht mit meinem ursprünglichen (fehlgeschlagenen) Skript gedruckt. Es sieht also so aus, als wäre dies tatsächlich ein Fehler beim Kompilieren , weshalb er nicht abgefangen wird.
JamesLean
@JamesLean zu lustig, als ich zum repro ging, um zu bestätigen, was Sie vorschlugen, hatte ich den sp_testlinkedserverAnruf auskommentiert , aber das SELECTals dynamisches SQL gelassen . Das PRINTtritt nicht auf, wenn Sie den Servernamen direkt referenzieren, BEGIN TRYda der Fehler zuerst ausgelöst wird.
Aaron Bertrand
4

Nach der Untersuchung sieht es so aus, als ob dieser Fehler nicht abgefangen wird, da es sich eher um einen Kompilierungsfehler als um einen Laufzeitfehler handelt. Versuchen Sie Folgendes, um dies zu demonstrieren:

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;

Die anfängliche PRINTAnweisung wird weder ausgegeben, noch wird der Fehler "Division durch Null" ausgeführt / abgefangen. Der nicht vorhandene Server führt dazu, dass das Skript sofort fehlschlägt.

JamesLean
quelle
3

Ich hatte kürzlich ein ähnliches Problem, bei dem ich eine entfernte Prozedur aus einem TRY-CATCH heraus aufgerufen habe und die Prozedur fehlgeschlagen ist, weil versucht wurde, einen doppelten Schlüssel einzufügen (Laufzeitfehler der Stufe 16). Der CATCH-Block wurde nicht aufgerufen. Ich habe den Grund in diesem Artikel gefunden: https://technet.microsoft.com/en-us/library/ms191515(v=sql.105).aspx

Die Lösung besteht darin, XACT_ABORT in der aufrufenden Prozedur auf ON zu setzen, bevor die entfernte Prozedur aufgerufen wird. Wenn XACT_ABORT aktiviert ist, wird der CATCH-Block wie erwartet aufgerufen. Sie müssen sich bewusst sein, dass die Einstellung XACT_ABORT an die Remote-Prozedur weitergegeben wird. Dies kann sich auf deren Verhalten auswirken.

RM Buda
quelle
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
quelle
1
Hallo, zunächst einmal herzlich willkommen auf der Seite. Hier möchten wir ein bisschen erklären, wie die Dinge funktionieren, anstatt eine Code-Wand ohne zusätzliche Informationen. Aber danke für die Antwort.
Tom V - Team Monica