Пов’язана помилка сервера, яку 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 (як правильно вказав Макс), щоб відкласти аналізатор, що підтверджує ім'я сервера до часу виконання.

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 занадто смішно, коли я пішов на запитання, щоб підтвердити те, що ви пропонували, я прокоментував 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.