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级)。

有谁知道为什么不能正确捕获这些错误?

Answers:


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(并且以上确实可以正常工作)。我对为什么未发现错误感兴趣?
JamesLean 2013年

@AaronBertrand如果PRINT 'Start';在脚本的顶部添加一个简单的脚本,即使连接失败并且脚本因错误而退出,该脚本也会在输出中打印出来。因此,这表明不是运行时错误吗?除非我误会了?
JamesLean 2013年

Gah,当我添加时,脚本中PRINT仍然有sp_testlinkedserver呼叫。实际上,它不是使用我的原始(失败)脚本打印的。因此看起来这实际上是一个编译时错误,这就是为什么它不会被捕获的原因。
JamesLean 2013年

@JamesLean太有趣了,当我去repro确认您的建议时,我已经注释掉了该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/zh-cn/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
嗨,首先,欢迎来到该网站。在这里,我们喜欢对事物如何工作的一些解释,而不是没有附加信息的代码墙。但是,谢谢您的回答。
汤姆五世-试试topanswers.xyz16年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.