Ошибка связанного сервера не обнаружена 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).

Кто-нибудь знает, почему эти ошибки не улавливаются правильно?

JamesLean
источник

Ответы:

11

Одна вещь, которую вы можете попробовать, это использовать sp_testlinkedserver. Вы также можете OPENQUERYиспользовать динамический SQL (как правильно указал Макс), чтобы отложить анализатор, проверяющий имя сервера до времени выполнения.

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
@AaronBertrand Если вы добавляете простое PRINT 'Start';в самом верху сценария, это выводится на печать в выводе, даже если затем происходит сбой соединения и сценарий завершается с ошибкой. Так что это будет указывать на ошибку во время выполнения , не так ли? Если я не понимаю это?
JamesLean
Гах, когда я добавил, у PRINTменя все еще был sp_testlinkedserverвызов в сценарии. На самом деле, он не печатается с использованием моего оригинального (неудачного) скрипта. Таким образом, похоже, что это на самом деле ошибка во время компиляции, поэтому она не перехватывается.
JamesLean
@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оператор не получает вывод, и при этом ошибка деления на ноль не выполняется / перехватывается. Несуществующий сервер вызывает немедленный сбой сценария.

JamesLean
источник
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