web-dev-qa-db-ja.com

リンクサーバーエラーはTRY-CATCHでキャッチされません

リンクサーバーのリストをループして、それぞれに対して特定のクエリを実行するジョブを設定しています。 TRY-CATCHブロック内でクエリを実行しようとしているので、特定のサーバーに問題がある場合はログに記録して、他のサーバーで続行できます。

ループ内で実行しているクエリは次のようになります。

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

サーバーへの接続に問題がある場合、コードはただちに失敗し、CATCHブロックに転送されません。サーバーは接続するが、実際のクエリにエラーがある場合。ゼロで除算すると、これはCATCHブロックで期待どおりにキャッチされます。

たとえば、リンクサーバーを、存在しないことがわかっている名前で作成しました。上記を実行すると、次のようになります。

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

私はTRY-CATCHでBOLを読みましたが、接続を切断するレベル20以上のエラーは検出されませんが、そうではないようです(これはレベル16のみです)。

これらのエラーが正しくキャッチされない理由を誰かが知っていますか?

14
JamesLean

あなたが試すことができる一つのことは sp_testlinkedserver を使うことです。 (Maxが正しく指摘したように)動的SQLを使用してOPENQUERYを発行し、ランタイムまでサーバー名を検証するパーサーを延期することもできます。

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

これはsp_testlinkedserverがなくても同じように機能しますが、その手順は、そのサーバーに対して大量のコードを試行することを防ぐのに役立ちます...


また、sp_testlinkedserverが失敗した場合、実際にコンパイル時に失敗するため、それを延期して、動的SQLを使用してキャプチャすることもできます。

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

このようなことを試しましたか?

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;

以下のコメントのとおり、コンパイル時にエラーが生成されなくなったため、これは機能します。エラーは、実行時にsp_executesqlストアドプロシージャ内で発生するようになりました。

6
Max Vernon

調査の結果、このエラーはランタイムエラーではなくcompile-timeエラーであるため、捕捉されていないようです。これを実証するには、次のことを試してください。

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;

最初のPRINTステートメントは出力を取得せず、ゼロ除算エラーも実行/キャッチされません。存在しないサーバーにより、スクリプトはすぐに失敗します。

4
JamesLean

最近、TRY-CATCH内からリモートプロシージャを呼び出し、重複するキーを挿入しようとしたためにプロシージャが失敗するという同様の問題がありました(レベル16ランタイムエラー)。 CATCHブロックは呼び出されませんでした。この記事で理由を見つけました: https://technet.Microsoft.com/en-us/library/ms191515(v = sql.105).aspx

解決策は、リモートプロシージャを呼び出す前に、呼び出し側のプロシージャでXACT_ABORT ONを設定することです。 XACT_ABORTがオンの場合、CATCHブロックが期待どおりに呼び出されます。 XACT_ABORT設定がリモートプロシージャに伝達され、その動作に影響する可能性があることに注意する必要があります。

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