TRY-CATCHでキャッチされないリンクサーバーエラー


14

リンクサーバーのリストをループし、各サーバーに対して特定のクエリを実行するジョブを設定しています。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].

私はBOLを読んでTRY-CATCH、接続を切断するレベル20以上のエラーをキャッチしないことを知っていますが、これは事実ではないようです(これはレベル16のみです)。

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

回答:


11

試すことができるのは、を使用することsp_testlinkedserverです。また、OPENQUERY動的SQLを使用して(Maxが正しく指摘されているように)発行し、実行時までサーバー名を検証するパーサーを延期することもできます。

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

6

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

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ストアドプロシージャ内で発生します。


ありがとう はい、私は動的SQLについて考えていました(そして上記は実際に正しく動作します)。エラーがキャッチされない理由に興味がありますか?
ジェームズリーン

@AaronBertrand PRINT 'Start';スクリプトの最上部にシンプルを追加すると、接続が失敗し、スクリプトがエラーで終了した場合でも、出力に出力されます。だから、これはランタイムエラーではないことを示しますか?誤解しない限り?
ジェームズリーン

ガ、私が追加したとき、PRINT私はまだsp_testlinkedserverスクリプトに呼び出しがありました。実際、元の(失敗した)スクリプトを使用して印刷されません。したがって、これは実際にはコンパイル時エラーであるように見えます。そのため、このエラーは捕捉されません。
ジェームズリーン

@JamesLeanおかしい、あなたが提案していることを確認するために再現に行ったとき、私はsp_testlinkedserver呼び出しをコメントアウトしましたが、SELECT動的SQLのままにしておきました。PRINTあなたが直接、サーバー名を参照する場合、私は、以前に提案したように、発生しないBEGIN TRYエラーが最初に提起されているため、入力されることはありません。
アーロンバートランド

4

調査後、このエラーは実行エラーではなくコンパイル時エラーであるため、キャッチされていないようです。これを実証するには、次を試してください。

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ステートメントは出力を取得せず、ゼロ除算エラーも実行/キャッチされません。存在しないサーバーは、スクリプトをすぐに失敗させます。


3

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

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


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
こんにちは、まずサイトへようこそ。ここでは、追加情報のないコードの壁ではなく、物事がどのように機能するかについての少しの説明が好きです。しかし、答えてくれてありがとう。
トムV-topanswers.xyzを試す16
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.