it-swarm.com.de

Verbindungsserverfehler nicht von TRY-CATCH abgefangen

Ich richte einen Job ein, um eine Liste von Verbindungsservern zu durchlaufen und für jeden eine bestimmte Abfrage auszuführen. Ich versuche, die Abfrage in einem TRY-CATCH-Block auszuführen. Wenn also ein Problem mit einem bestimmten Server auftritt, 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 einer Verbindung zum Server ein Problem auftritt, schlägt der Code sofort fehl und wird nicht in den Block CATCH übertragen. Wenn der Server eine Verbindung herstellt, aber ein Fehler in der tatsächlichen Abfrage vorliegt, z. durch Null teilen, dann wird dies wie erwartet vom Block CATCH abgefangen.

Ich habe beispielsweise einen Verbindungsserver mit einem Namen erstellt, von dem ich weiß, dass er nicht vorhanden ist. Wenn ich das oben genannte ausführe, 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 auf TRY-CATCH Gelesen und weiß, dass es keine Fehler der 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?

14
JamesLean

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

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

Dies funktioniert zwar genauso gut ohne sp_testlinkedserver, diese Prozedur kann immer noch nützlich sein, um zu verhindern, dass Sie eine ganze Reihe von Code gegen diesen Server versuchen ...


Auch seit if sp_testlinkedserver schlägt fehl, es schlägt tatsächlich zur Kompilierungszeit fehl. Sie können dies verschieben und trotzdem erfassen, indem Sie dort auch dynamisches SQL verwenden:

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

Haben Sie so etwas versucht?

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;

Gemäß den Kommentaren unten funktioniert dies, da der Fehler beim Kompilieren nicht mehr generiert wird. Der Fehler tritt jetzt zur Laufzeit in der gespeicherten Prozedur sp_executesql auf.

6
Max Vernon

Nach der Untersuchung sieht es so aus, als würde dieser Fehler nicht abgefangen, da es sich eher um einen Kompilierungszeit Fehler 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 Anweisung PRINT wird weder ausgegeben, noch wird der Fehler zum Teilen durch Null ausgeführt/abgefangen. Der nicht vorhandene Server führt dazu, dass das Skript sofort fehlschlägt.

4
JamesLean

Ich hatte kürzlich ein ähnliches Problem, bei dem ich eine Remote-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 SET zu setzen, bevor die Remote-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.

3
R.M. Buda
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;
0
Jeffrey W Lott