Erro no servidor vinculado não detectado pelo TRY-CATCH

14

Estou configurando um trabalho para percorrer uma lista de servidores vinculados e executar uma consulta específica em cada um. Estou tentando executar a consulta dentro de um bloco TRY-CATCH, portanto, se houver um problema com um servidor específico, eu posso registrá-lo, mas depois continuar com os outros servidores.

A consulta que estou executando dentro do loop é mais ou menos assim:

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

Se houver um problema na conexão com o servidor, o código falhará imediatamente e não será transferido para o servidor. CATCH bloco. Se o servidor se conectar, mas houver um erro na consulta real, por exemplo, dividir por zero, isso será capturado conforme o esperado pelo CATCHbloco.

Por exemplo, criei um servidor vinculado a um nome que sei que não existe. Ao executar o acima, eu apenas recebo:

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

Eu li BOL em TRY-CATCH e sei que ele não detectará erros de nível 20+ que interrompem a conexão, mas isso não parece ser o caso (este é apenas o nível 16).

Alguém sabe por que esses erros não foram detectados corretamente?

JamesLean
fonte

Respostas:

11

Uma coisa que você pode tentar é usar sp_testlinkedserver. Você também pode emitir o OPENQUERYSQL dinâmico usando (como Max apontou corretamente), para adiar o analisador que valida o nome do servidor até o tempo de execução.

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

Embora isso funcione igualmente bem sem sp_testlinkedserver, esse procedimento ainda pode ser útil para impedir que você tente um monte de código nesse servidor ...


Além disso, como se sp_testlinkedserverfalha, na verdade, falha no momento da compilação, você pode adiar e ainda capturá-lo usando o SQL dinâmico também:

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

Você já tentou algo assim?

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;

Conforme os comentários abaixo, isso funciona, pois o erro não é mais gerado no tempo de compilação. O erro agora ocorre em tempo de execução, dentro do procedimento armazenado sp_executesql.

Max Vernon
fonte
Obrigado Max. Sim, eu pensei em SQL dinâmico (e o acima realmente funciona corretamente). Estou interessado em POR QUE o erro não foi detectado?
precisa saber é o seguinte
@AaronBertrand Se você adicionar um simples PRINT 'Start';na parte superior do script, isso será impresso na saída, mesmo que a conexão falhe e o script saia com o erro. Então isso indicaria um erro de tempo de execução, não é? A menos que eu esteja entendendo errado?
precisa saber é o seguinte
Gah, quando adicionei PRINT, ainda tinha a sp_testlinkedserverchamada no script. Na verdade, ele não é impresso usando meu script original (com falha). Portanto, parece que esse é realmente um erro em tempo de compilação, e é por isso que não é pego.
21413 JamesLean
@ JamesLean muito engraçado, quando fui repro para confirmar o que você estava sugerindo, eu havia comentado a sp_testlinkedserverligação, mas deixei o SELECTSQL como dinâmico. A PRINTnão ocorre se você faz referência o nome do servidor diretamente, assim como eu tinha sugerido anteriormente, BEGIN TRYnunca é introduzido porque o erro é gerado pela primeira vez.
Aaron Bertrand
4

Após a investigação, parece que esse erro não está sendo capturado, pois é um erro em tempo de compilação e não um erro de tempo de execução. Para demonstrar isso, tente o seguinte:

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;

A PRINTinstrução inicial não obtém saída, nem o erro de divisão por zero é executado / capturado. O servidor inexistente faz com que o script falhe imediatamente.

JamesLean
fonte
3

Recentemente, tive um problema semelhante ao chamar um procedimento remoto de dentro de um TRY-CATCH e o procedimento falhou devido à tentativa de inserir chave duplicada (erro de tempo de execução no nível 16). O bloco CATCH não foi chamado. Encontrei o motivo neste artigo: https://technet.microsoft.com/en-us/library/ms191515(v=sql.105).aspx

A solução é SET XACT_ABORT ON no procedimento de chamada antes de chamar o procedimento remoto. Quando XACT_ABORT está no bloco CATCH, é invocado conforme o esperado. Você precisa estar ciente de que a configuração XACT_ABORT é propagada para o procedimento remoto e isso pode afetar seu comportamento.

RM Buda
fonte
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
fonte
1
Olá, antes de tudo, bem-vindo ao site. Por aqui, gostamos de algumas explicações sobre como as coisas funcionam, em vez de uma parede de código sem informações adicionais. Mas obrigado pela resposta.
Tom V - tentativa topanswers.xyz