Bağlantılı sunucu hatası TRY-CATCH tarafından yakalanmadı


14

Bağlı sunucular listesi üzerinden döngü ve her birine karşı belirli bir sorgu yürütmek için bir iş kuruyorum. Belirli bir sunucu ile ilgili bir sorun varsa ben oturum ama daha sonra diğer sunucular ile devam böylece bir TRY-CATCH bloğu içinde sorgu yürütmek çalışıyorum.

Döngü içinde yürüttüğüm sorgu şöyle görünür:

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

Sunucuya bağlanırken bir sorun varsa, kod hemen başarısız olur ve CATCHbloğa aktarılmaz . Sunucu bağlanır, ancak asıl sorguda bir hata varsa, örneğin sıfıra bölme, bu CATCHblok tarafından beklendiği gibi yakalanır .

Örneğin, var olmadığını bildiğim bir adla bağlantılı bir sunucu oluşturdum. Yukarıdakileri yürütürken sadece alıyorum:

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 okudum TRY-CATCHve bağlantıyı kesen seviye 20 + hataları yakalamayacağını biliyorum ama bu böyle görünmüyor (bu sadece seviye 16).

Bu hataların neden doğru bir şekilde yakalanmadığını bilen var mı?

Yanıtlar:


11

Deneyebileceğiniz bir şey kullanmaktır sp_testlinkedserver. OPENQUERYAyrıştırıcının çalışma zamanına kadar sunucu adını doğrulamasını ertelemek için dinamik SQL (Max doğru şekilde işaret edildiği gibi) kullanarak da verebilirsiniz .

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

Bu olmadan eşit derecede iyi çalışıyor olsa da sp_testlinkedserver, bu prosedür yine de bu sunucuya karşı bir sürü kod denemenizi önlemede yararlı olabilir ...


Ayrıca, eğer sp_testlinkedserverbaşarısız olursa derleme zamanında başarısız olursa, bunu erteleyebilir ve orada dinamik SQL kullanarak da yakalayabilirsiniz:

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

6

Böyle bir şey denedin mi?

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;

Aşağıdaki yorumlara göre, hata derleme zamanında üretilmediğinden bu çalışır. Hata artık çalışma zamanında, sp_executesql saklı yordamı içinde oluşur.


Teşekkürler Max. Evet, dinamik SQL düşündüm (ve yukarıdakiler gerçekten doğru çalışıyor). Hatanın neden yakalanmadığı ile ilgileniyorum?
JamesLean

@AaronBertrand PRINT 'Start';Komut dosyasının en üstüne bir basit eklerseniz , bağlantı başarısız olursa ve komut dosyası hatayla çıksa bile bu çıktıda yazdırılır. Yani bu bir çalışma zamanı hatası olduğunu gösterir değil mi? Yanlış anlamadığım sürece?
JamesLean

Gah, eklediğimde PRINThala sp_testlinkedserversenaryoda çağrı vardı . Aslında, orijinal (başarısız) betiğimi kullanarak yazdırılmaz. Bu aslında derleme zamanı hatası gibi görünüyor , bu yüzden yakalanmıyor.
JamesLean

@JamesLean çok komik, ne önerdiğini onaylamak için repro gittiğimde, ben sp_testlinkedserverçağrı yorumladı , ama SELECTdinamik SQL olarak bıraktı . PRINTDoğrudan sunucu adını başvurursanız daha önce önerdi böylece, oluşmaz, BEGIN TRYhata birinci kalkık olduğundan girilen hiçbir zaman.
Aaron Bertrand

4

Soruşturmadan sonra, bu hata bir çalışma zamanı hatası yerine derleme zamanı hatası olduğu için yakalanmıyor gibi görünüyor . Bunu göstermek için aşağıdakileri deneyin:

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;

İlk PRINTifade çıktı almaz veya sıfır hataya bölme işlemi gerçekleştirilmez / yakalanmaz. Var olmayan sunucu komut dosyasının hemen başarısız olmasına neden olur.


3

Geçenlerde bir TL-CATCH içinde uzak bir yordam çağırdı ve yordam yinelenen anahtar (düzey 16 çalışma süresi hatası) ekleme girişimi nedeniyle başarısız oldu benzer bir sorun vardı. CATCH bloğu çağrılmadı. Nedeni bu makalede buldum: https://technet.microsoft.com/en-us/library/ms191515(v=sql.105).aspx

Çözüm, uzak yordamı başlatmadan önce çağrı prosedüründe XACT_ABORT ON ayarlanmasıdır. XACT_ABORT açık olduğunda CATCH bloğu beklendiği gibi çağrılır. XACT_ABORT ayarının uzak yordama yayıldığını ve bunun davranışını etkileyebileceğini bilmeniz gerekir.


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
Merhaba, her şeyden önce siteye hoş geldiniz. Burada, ek bilgi içermeyen bir kod duvarı yerine işlerin nasıl çalıştığı hakkında biraz açıklama yapmayı seviyoruz. Ama cevap için teşekkürler.
Tom V - topanswers.xyz deneyin
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.