Error del servidor vinculado no detectado por TRY-CATCH

14

Estoy configurando un trabajo para recorrer una lista de servidores vinculados y ejecutar una consulta específica para cada uno. Estoy tratando de ejecutar la consulta dentro de un bloque TRY-CATCH, así que si hay un problema con un servidor en particular, puedo iniciar sesión pero luego continuar con los otros servidores.

La consulta que estoy ejecutando dentro del bucle se ve así:

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

Si hay un problema al conectarse al servidor, el código simplemente falla inmediatamente y no se transfiere al CATCHbloque. Si el servidor se conecta pero hay un error en la consulta real, por ejemplo, dividir por cero, entonces el CATCHbloque lo detecta como se esperaba .

Por ejemplo, creé un servidor vinculado a un nombre que sé que no existe. Al ejecutar lo anterior solo obtengo:

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

He leído BOL TRY-CATCHy sé que no detectará errores de nivel 20+ que rompan la conexión, pero este no parece ser el caso (este es solo el nivel 16).

¿Alguien sabe por qué estos errores no se detectan correctamente?

JamesLean
fuente

Respuestas:

11

Una cosa que puedes probar es usar sp_testlinkedserver. También puede emitir el OPENQUERYuso de SQL dinámico (como señaló Max correctamente), para diferir el analizador que valida el nombre del servidor hasta el tiempo de ejecución.

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

Si bien esto funciona igualmente bien sin él sp_testlinkedserver, ese procedimiento aún puede ser útil para evitar que pruebe un montón de código en ese servidor ...


Además, dado que si sp_testlinkedserverfalla, en realidad falla en el momento de la compilación, puede diferir eso y aún capturarlo usando SQL dinámico allí también:

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

¿Has intentado algo como ésto?

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;

Según los comentarios a continuación, esto funciona ya que el error ya no se genera en tiempo de compilación. El error ahora ocurre en tiempo de ejecución, dentro del procedimiento almacenado sp_executesql.

Max Vernon
fuente
Gracias Max. Sí, había pensado en SQL dinámico (y lo anterior realmente funciona correctamente). Estoy interesado en ¿POR QUÉ no se detecta el error?
JamesLean
@AaronBertrand Si agrega un simple PRINT 'Start';en la parte superior de la secuencia de comandos, esto se imprime en la salida, a pesar de que la conexión falla y la secuencia de comandos se cierra con el error. Entonces, esto indicaría un error de tiempo de ejecución, ¿no? ¿A menos que lo esté malentendiendo?
JamesLean
Gah, cuando agregué el PRINTtodavía tenía la sp_testlinkedserverllamada en el guión. De hecho, no se imprime usando mi secuencia de comandos original (fallida). Entonces parece que esto es en realidad un error en tiempo de compilación , por lo que no queda atrapado.
JamesLean
@JamesLean demasiado divertido, cuando fui a repro para confirmar lo que estabas sugiriendo, había comentado la sp_testlinkedserverllamada, pero dejé el SELECTSQL dinámico. El PRINTno ocurre si hace referencia el nombre del servidor directamente, por lo que me había sugerido anteriormente, BEGIN TRYno se introduce porque el error se planteó por primera vez.
Aaron Bertrand
4

Después de la investigación, parece que este error no se detecta, ya que es un error en tiempo de compilación en lugar de un error en tiempo de ejecución. Para demostrar esto, intente lo siguiente:

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;

La PRINTdeclaración inicial no obtiene salida, ni el error de división por cero se ejecuta / captura. El servidor inexistente hace que el script falle inmediatamente.

JamesLean
fuente
3

Recientemente tuve un problema similar en el que llamé a un procedimiento remoto desde un TRY-CATCH y el procedimiento falló debido al intento de insertar una clave duplicada (error de tiempo de ejecución de nivel 16). El bloque CATCH no fue invocado. Encontré la razón en este artículo: https://technet.microsoft.com/en-us/library/ms191515(v=sql.105).aspx

La solución es SET XACT_ABORT ON en el procedimiento de llamada antes de invocar el procedimiento remoto. Cuando XACT_ABORT está en el bloque CATCH, se invoca como se esperaba. Debe tener en cuenta que la configuración XACT_ABORT se propaga al procedimiento remoto y eso puede afectar su comportamiento.

RM Buda
fuente
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
fuente
1
Hola, en primer lugar bienvenido al sitio. Por aquí nos gusta un poco de explicación sobre cómo funcionan las cosas en lugar de un muro de código sin información adicional. Pero gracias por la respuesta.
Tom V - prueba topanswers.xyz