Lỗi máy chủ được liên kết không bị bắt bởi TRY-CATCH


14

Tôi đang thiết lập một công việc để lặp qua danh sách các máy chủ được liên kết và thực hiện một truy vấn cụ thể đối với từng máy chủ. Tôi đang cố gắng thực hiện truy vấn bên trong khối TRY-CATCH để nếu có vấn đề với một máy chủ cụ thể, tôi có thể đăng nhập nhưng sau đó tiếp tục với các máy chủ khác.

Truy vấn tôi đang thực hiện bên trong vòng lặp trông giống như thế này:

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

Nếu có sự cố khi kết nối với máy chủ, mã sẽ bị lỗi ngay lập tức và không chuyển sang CATCHkhối. Nếu máy chủ kết nối nhưng có lỗi trong truy vấn thực tế, ví dụ: chia cho 0, thì điều này được bắt theo dự kiến ​​của CATCHkhối.

Ví dụ: tôi đã tạo một máy chủ được liên kết với một tên mà tôi biết không tồn tại. Khi thực hiện những điều trên tôi chỉ nhận được:

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

Tôi đã đọc BOL trên TRY-CATCHvà biết rằng nó sẽ không bắt lỗi cấp 20+ phá vỡ kết nối nhưng dường như không phải vậy (đây chỉ là cấp 16).

Có ai biết tại sao những lỗi này không được bắt chính xác?

Câu trả lời:


11

Một điều bạn có thể thử là sử dụng sp_testlinkedserver. Bạn cũng có thể phát hành OPENQUERYSQL động (như Max đã chỉ ra một cách chính xác), để trì hoãn trình phân tích cú pháp xác thực tên máy chủ cho đến khi chạy.

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

Mặc dù điều này hoạt động tốt như nhau mà không có sp_testlinkedserver, quy trình đó vẫn có thể hữu ích trong việc ngăn bạn thử cả đống mã chống lại máy chủ đó ...


Ngoài ra, vì nếu sp_testlinkedserverthất bại nó thực sự thất bại tại thời gian biên dịch, bạn có thể trì hoãn điều đó và vẫn nắm bắt nó bằng cách sử dụng SQL động ở đó:

BEGIN TRY
  EXEC master.sys.sp_executesql N'EXEC sp_testlinkedserver N''server1'';';
  ...
END TRY

6

Bạn đã thử một cái gì đó như thế này?

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;

Theo các ý kiến ​​dưới đây, điều này hoạt động do lỗi không còn được tạo tại thời gian biên dịch. Lỗi bây giờ xảy ra trong thời gian chạy, bên trong thủ tục lưu trữ sp_executesql.


Cảm ơn Max. Vâng, tôi đã nghĩ về SQL động (và ở trên thực sự hoạt động chính xác). Tôi quan tâm đến TẠI SAO lỗi không bị bắt mặc dù?
JamesLean

@AaronBertrand Nếu bạn thêm một đơn giản PRINT 'Start';ở đầu tập lệnh, điều này sẽ được in ở đầu ra, mặc dù kết nối sau đó không thành công và tập lệnh thoát khỏi lỗi. Vì vậy, điều này sẽ chỉ ra một lỗi thời gian chạy phải không? Trừ khi tôi hiểu lầm nó?
JamesLean

Gah, khi tôi thêm PRINTtôi vẫn có sp_testlinkedservercuộc gọi trong kịch bản. Trong thực tế, nó không được in bằng cách sử dụng tập lệnh gốc (không thành công) của tôi. Vì vậy, có vẻ như đây thực sự là một lỗi thời gian biên dịch , đó là lý do tại sao nó không bị bắt.
JamesLean

@JamesLean quá buồn cười, khi tôi đi repro để xác nhận những gì bạn đang đề xuất, tôi đã nhận xét sp_testlinkedservercuộc gọi, nhưng để lại SELECTSQL động. Điều PRINTnày không xảy ra nếu bạn tham chiếu trực tiếp tên máy chủ, như tôi đã đề xuất trước đó, BEGIN TRYkhông bao giờ được nhập vì lỗi được nêu ra trước tiên.
Aaron Bertrand

4

Sau khi điều tra, có vẻ như lỗi này không bị bắt vì đây là lỗi thời gian biên dịch thay vì lỗi thời gian chạy. Để chứng minh điều này, hãy thử như sau:

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;

Câu PRINTlệnh ban đầu không nhận được đầu ra, cũng như việc chia cho số không được thực hiện / bắt lỗi. Máy chủ không tồn tại khiến tập lệnh bị lỗi ngay lập tức.


3

Gần đây tôi đã gặp một vấn đề tương tự khi tôi gọi một thủ tục từ xa trong TRY-CATCH và quy trình không thành công do cố gắng chèn khóa trùng lặp (lỗi thời gian chạy cấp 16). Khối CATCH không được gọi. Tôi đã tìm thấy lý do trong bài viết này: https://technet.microsoft.com/en-us/l Library / ms191515 (v = sql.105) .aspx

Giải pháp là TẬP XACT_ABORT ON trong quy trình gọi trước khi gọi thủ tục từ xa. Khi XACT_ABORT ở trên khối CATCH được gọi như mong đợi. Bạn cần lưu ý rằng cài đặt XACT_ABORT được truyền đến thủ tục từ xa và điều đó có thể ảnh hưởng đến hành vi của nó.


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
Xin chào, trước hết chào mừng đến với trang web. Xung quanh đây, chúng tôi thích một chút giải thích về cách mọi thứ hoạt động thay vì một bức tường mã không có thêm thông tin. Nhưng cảm ơn vì câu trả lời.
Tom V - thử topanswers.xyz
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.