Galat server tertaut tidak ditangkap oleh TRY-CATCH

14

Saya menyiapkan tugas untuk mengulang daftar server yang ditautkan dan menjalankan kueri tertentu terhadap masing-masing server. Saya mencoba untuk mengeksekusi query di dalam blok TRY-CATCH jadi jika ada masalah dengan satu server tertentu saya bisa login tetapi kemudian melanjutkan dengan server lain.

Kueri yang saya jalankan di dalam loop terlihat seperti ini:

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

Jika ada masalah saat menyambung ke server, kode itu langsung gagal dan tidak dapat ditransfer ke CATCHblok. Jika server terhubung tetapi ada kesalahan dalam kueri yang sebenarnya, mis. Bagi dengan nol, maka ini ditangkap seperti yang diharapkan oleh CATCHblok.

Misalnya, saya membuat server tertaut ke nama yang saya tahu tidak ada. Saat menjalankan hal di atas saya baru saja mendapatkan:

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

Saya telah membaca BOL TRY-CATCHdan tahu bahwa itu tidak akan menangkap kesalahan level 20+ yang memutuskan koneksi tetapi ini tampaknya tidak menjadi masalah (ini hanya level 16).

Adakah yang tahu mengapa kesalahan ini tidak ditangkap dengan benar?

JamesLean
sumber

Jawaban:

11

Satu hal yang bisa Anda coba adalah menggunakan sp_testlinkedserver. Anda juga dapat mengeluarkan OPENQUERYpenggunaan dinamis SQL (seperti yang ditunjukkan Max dengan benar), untuk menunda parser memvalidasi nama server hingga runtime.

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

Meskipun ini berfungsi sama baiknya tanpa sp_testlinkedserver, prosedur itu masih bisa berguna dalam mencegah Anda dari mencoba sejumlah besar kode terhadap server itu ...


Juga, karena jika sp_testlinkedservergagal sebenarnya gagal pada waktu kompilasi, Anda dapat menunda itu dan masih menangkapnya dengan menggunakan SQL dinamis di sana juga:

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

Sudahkah Anda mencoba yang seperti ini?

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;

Seperti komentar di bawah, ini berfungsi karena kesalahan tidak lagi dihasilkan pada waktu kompilasi. Kesalahan sekarang terjadi saat runtime, di dalam prosedur tersimpan sp_executesql.

Max Vernon
sumber
Max terima kasih. Ya, saya telah memikirkan SQL dinamis (dan di atas memang berfungsi dengan benar). Saya tertarik pada MENGAPA kesalahannya tidak tertangkap?
JamesLean
@ AaronBertrand Jika Anda menambahkan sederhana PRINT 'Start';di bagian paling atas skrip, ini akan dicetak dalam output, meskipun koneksi kemudian gagal dan skrip keluar dengan kesalahan. Jadi ini akan menunjukkan kesalahan runtime bukan? Kecuali saya salah paham?
JamesLean
Gah, ketika saya menambahkan PRINTsaya masih memiliki sp_testlinkedserverpanggilan dalam skrip. Sebenarnya, itu tidak bisa dicetak menggunakan skrip asli saya (gagal). Jadi sepertinya ini sebenarnya adalah kesalahan kompilasi-waktu , yang mengapa tidak ketahuan.
JamesLean
@ JamesLean terlalu lucu, ketika saya pergi ke repro untuk mengkonfirmasi apa yang Anda sarankan, saya telah berkomentar sp_testlinkedserverpanggilan keluar , tetapi meninggalkan SELECTSQL dinamis. Tidak PRINTterjadi jika Anda merujuk nama server secara langsung, jadi seperti yang saya sarankan sebelumnya, BEGIN TRYtidak pernah dimasukkan karena kesalahan dinaikkan terlebih dahulu.
Aaron Bertrand
4

Setelah penyelidikan sepertinya kesalahan ini tidak ditangkap karena ini adalah kesalahan waktu kompilasi daripada kesalahan runtime. Untuk menunjukkan ini, coba yang berikut ini:

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;

PRINTPernyataan awal tidak mendapatkan output, juga tidak membagi kesalahan dengan nol dieksekusi / ditangkap. Server yang tidak ada menyebabkan skrip segera gagal.

JamesLean
sumber
3

Saya baru-baru ini memiliki masalah yang sama di mana saya memanggil prosedur jarak jauh dari dalam TRY-CATCH dan prosedur gagal karena upaya untuk memasukkan kunci duplikat (kesalahan run time level 16). Blok CATCH tidak dipanggil. Saya menemukan alasannya dalam artikel ini: https://technet.microsoft.com/en-us/library/ms191515(v=sql.105).aspx

Solusinya adalah SET XACT_ABORT AKTIF dalam prosedur panggilan sebelum memanggil prosedur jarak jauh. Ketika XACT_ABORT berada di blok CATCH dipanggil seperti yang diharapkan. Anda perlu mengetahui bahwa pengaturan XACT_ABORT disebarkan ke prosedur jarak jauh, dan itu mungkin mempengaruhi perilakunya.

RM Buda
sumber
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
sumber
1
Halo, selamat datang di situs ini. Di sini, kami menyukai sedikit penjelasan tentang cara kerja daripada dinding kode tanpa informasi tambahan. Tapi terima kasih atas jawabannya.
Tom V - coba topanswers.xyz