ข้อผิดพลาดเซิร์ฟเวอร์ที่เชื่อมโยงไม่ถูกจับโดย 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 แบบไดนามิก (ตามที่ Max ระบุอย่างถูกต้อง) เพื่อเลื่อน parser ที่ตรวจสอบความถูกต้องของชื่อเซิร์ฟเวอร์จนกระทั่งรันไทม์

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

Gah เมื่อฉันเพิ่มPRINTฉันยังคงมีการsp_testlinkedserverโทรในสคริปต์ อันที่จริงแล้วมันไม่ได้รับการพิมพ์โดยใช้สคริปต์ดั้งเดิมของฉัน (ล้มเหลว) ดังนั้นดูเหมือนว่านี่เป็นข้อผิดพลาดในการรวบรวมเวลาซึ่งเป็นเหตุผลว่าทำไมจึงไม่ติด
JamesLean

@ JamesLean ตลกเกินไปเมื่อฉันไปทำซ้ำเพื่อยืนยันสิ่งที่คุณได้รับการแนะนำฉันได้แสดงความคิดเห็นออกsp_testlinkedserverสาย แต่เหลือSELECTSQL แบบไดนามิก PRINTไม่เกิดขึ้นถ้าคุณอ้างอิงชื่อเซิร์ฟเวอร์โดยตรงเพื่อที่ผมได้แนะนำก่อนหน้านี้BEGIN TRYก็ไม่เคยเข้ามาเพราะข้อผิดพลาดจะเพิ่มขึ้นเป็นครั้งแรก
Aaron Bertrand

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

การแก้ปัญหาคือการ SET 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
สวัสดีก่อนอื่นยินดีต้อนรับสู่เว็บไซต์ รอบ ๆ ที่นี่เราชอบคำอธิบายเล็กน้อยเกี่ยวกับวิธีการทำงานของสิ่งต่าง ๆ มากกว่าส่วนของรหัสโดยไม่มีข้อมูลเพิ่มเติม แต่ขอบคุณสำหรับคำตอบ
Tom V - ลอง topanswers.xyz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.