ในกรณีใดธุรกรรมสามารถกระทำจากภายในบล็อกจับเมื่อ XACT_ABORT ตั้งเป็น ON?


13

ฉันได้อ่านเกี่ยวกับ MSDN และTRY...CATCHXACT_STATE

มันมีตัวอย่างต่อไปนี้ที่ใช้XACT_STATEในCATCHบล็อกของการTRY…CATCHสร้างเพื่อตรวจสอบว่าจะกระทำหรือย้อนกลับการทำธุรกรรม:

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Test XACT_STATE for 0, 1, or -1.
    -- If 1, the transaction is committable.
    -- If -1, the transaction is uncommittable and should 
    --     be rolled back.
    -- XACT_STATE = 0 means there is no transaction and
    --     a commit or rollback operation would generate an error.

    -- Test whether the transaction is uncommittable.
    IF (XACT_STATE()) = -1
    BEGIN
        PRINT 'The transaction is in an uncommittable state.' +
              ' Rolling back transaction.'
        ROLLBACK TRANSACTION;
    END;

    -- Test whether the transaction is active and valid.
    IF (XACT_STATE()) = 1
    BEGIN
        PRINT 'The transaction is committable.' + 
              ' Committing transaction.'
        COMMIT TRANSACTION;   
    END;
END CATCH;
GO

สิ่งที่ฉันไม่เข้าใจคือเพราะเหตุใดฉันจึงควรดูแลและตรวจสอบสิ่งที่XACT_STATEส่งคืน?

โปรดทราบว่าการตั้งค่าสถานะXACT_ABORTเป็นONตัวอย่าง

หากมีข้อผิดพลาดรุนแรงพอที่ภายในบล็อกควบคุมจะผ่านเข้าไปTRY CATCHดังนั้นถ้าฉันอยู่ข้างในCATCHฉันรู้ว่าธุรกรรมมีปัญหาและสิ่งเดียวที่สมเหตุสมผลที่จะทำในกรณีนี้คือการย้อนกลับใช่ไหม?

แต่ตัวอย่างนี้จาก MSDN บอกเป็นนัยว่าอาจมีบางกรณีเมื่อการควบคุมถูกส่งผ่านเข้าไปCATCHและยังคงเหมาะสมที่จะยอมรับการทำธุรกรรม ใครสักคนให้ตัวอย่างที่เป็นประโยชน์เมื่อมันเกิดขึ้นได้เมื่อมันสมเหตุสมผล

ฉันไม่เห็นในสิ่งที่กรณีการควบคุมสามารถส่งผ่านภายในCATCHกับการทำธุรกรรมที่สามารถกระทำเมื่อXACT_ABORTONมีการตั้งค่า

บทความเกี่ยวกับ MSDN SET XACT_ABORTมีตัวอย่างเมื่อคำสั่งบางอย่างภายในธุรกรรมดำเนินการสำเร็จและบางความล้มเหลวเมื่อXACT_ABORTตั้งค่าเป็นOFFฉันเข้าใจว่า แต่ด้วยSET XACT_ABORT ONวิธีการมันจะเกิดขึ้นที่XACT_STATE()ส่งกลับ 1 ภายในCATCHบล็อก?

เริ่มแรกฉันจะเขียนโค้ดนี้เช่นนี้:

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Some severe problem with the transaction
    PRINT 'Rolling back transaction.';
    ROLLBACK TRANSACTION;
END CATCH;
GO

โดยคำนึงถึงคำตอบของ Max Vernon ฉันจะเขียนโค้ดแบบนี้ ROLLBACKเขาแสดงให้เห็นว่ามันทำให้ความรู้สึกที่จะตรวจสอบว่ามีการทำธุรกรรมที่ใช้งานก่อนที่จะพยายาม ยังคงมีบล็อกสามารถได้ถึงวาระทั้งการทำธุรกรรมหรือการทำธุรกรรมที่ไม่ทั้งหมด ดังนั้นในกรณีใด ๆ มีอะไรที่จะ ฉันผิดหรือเปล่า?SET XACT_ABORT ONCATCHCOMMIT

USE AdventureWorks2012;
GO

-- SET XACT_ABORT ON will render the transaction uncommittable
-- when the constraint violation occurs.
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;
        -- A FOREIGN KEY constraint exists on this table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction. The CATCH
    -- block will not execute.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Some severe problem with the transaction
    IF (XACT_STATE()) <> 0
    BEGIN
        -- There is still an active transaction that should be rolled back
        PRINT 'Rolling back transaction.';
        ROLLBACK TRANSACTION;
    END;

END CATCH;
GO

คำตอบ:


8

แต่กลับกลายเป็นว่าการทำธุรกรรมสามารถไม่ได้จะมุ่งมั่นจากภายในCATCHบล็อกถ้ามีการตั้งค่าXACT_ABORTON

ตัวอย่างจาก MSDN ค่อนข้างทำให้เข้าใจผิดเนื่องจากการตรวจสอบหมายถึงว่าXACT_STATEสามารถส่งคืน 1 ในบางกรณีและอาจเป็นไปได้ในCOMMITการทำธุรกรรม

IF (XACT_STATE()) = 1
BEGIN
    PRINT 'The transaction is committable.' + 
          ' Committing transaction.'
    COMMIT TRANSACTION;   
END;

มันไม่เป็นความจริงXACT_STATEจะไม่กลับ 1 ภายในCATCHบล็อกถ้ามีการตั้งค่าXACT_ABORTON

ดูเหมือนว่ารหัสตัวอย่าง MSDN มีวัตถุประสงค์เพื่อแสดงการใช้งานเป็นหลักXACT_STATE()โดยไม่คำนึงถึงการXACT_ABORTตั้งค่า ตัวอย่างรหัสดูพอทั่วไปเพื่อให้การทำงานมีทั้งXACT_ABORTชุดและON OFFเป็นเพียงว่าXACT_ABORT = ONการตรวจสอบIF (XACT_STATE()) = 1จะไม่จำเป็น


มีชุดบทความที่มีรายละเอียดที่ดีมากเกี่ยวกับข้อผิดพลาดและการจัดการธุรกรรมใน SQL Serverโดย Erland Sommarskog ในส่วนที่ 2 - การจำแนกประเภทของข้อผิดพลาดเขานำเสนอตารางที่ครอบคลุมที่รวบรวมคลาสของข้อผิดพลาดทั้งหมดและวิธีการจัดการโดย SQL Server และวิธีการTRY ... CATCHและXACT_ABORTการเปลี่ยนแปลงพฤติกรรม

+-----------------------------+---------------------------++------------------------------+
|                             |     Without TRY-CATCH     ||        With TRY-CATCH        |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
|              SET XACT_ABORT |  OFF  |  ON   | OFF | ON  ||    ON or OFF     | OFF | ON  |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Class Name                  |    Aborts     |   Rolls   ||    Catchable     |   Dooms   |
|                             |               |   Back    ||                  |transaction|
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+
| Fatal errors                |  Connection   |    Yes    ||       No         |    n/a    |
| Batch-aborting              |     Batch     |    Yes    ||       Yes        |    Yes    |
| Batch-only aborting         |     Batch     | No  | Yes ||       Yes        | No  | Yes |
| Statement-terminating       | Stmnt | Batch | No  | Yes ||       Yes        | No  | Yes |
| Terminates nothing at all   |    Nothing    |    No     ||       Yes        | No  | Yes |
| Compilation: syntax errors  |  (Statement)  |    No     ||       Yes        | No  | Yes |
| Compilation: binding errors | Scope | Batch | No  | Yes || Outer scope only | No  | Yes |
| Compilation: optimisation   |     Batch     |    Yes    || Outer scope only |    Yes    |
| Attention signal            |     Batch     | No  | Yes ||       No         |    n/a    |
| Informational/warning msgs  |    Nothing    |    No     ||       No         |    n/a    |
| Uncatchable errors          |    Varying    |  Varying  ||       No         |    n/a    |
+-----------------------------+-------+-------+-----+-----++------------------+-----+-----+

คอลัมน์สุดท้ายในตารางตอบคำถาม ด้วยTRY-CATCHและมีการXACT_ABORT ONทำธุรกรรมเป็นวาระในกรณีที่เป็นไปได้ทั้งหมด

One note นอกขอบเขตของคำถาม ในฐานะที่เป็น Erland กล่าวสอดคล้องนี้เป็นหนึ่งในเหตุผลที่จะชุดXACT_ABORTเพื่อON:

SET XACT_ABORT, NOCOUNT ONผมได้ให้คำแนะนำแล้วว่าขั้นตอนการจัดเก็บของคุณควรจะรวมถึงคำสั่ง หากคุณดูที่ตารางข้างต้นคุณจะเห็นว่าXACT_ABORTในความเป็นจริงแล้วจะมีระดับความสอดคล้องที่สูงกว่า ตัวอย่างเช่นการทำธุรกรรมจะถึงวาระเสมอ ในต่อไปนี้ฉันจะแสดงตัวอย่างมากมายที่ฉันตั้งไว้XACT_ABORTเพื่อOFFให้คุณสามารถเข้าใจว่าทำไมคุณควรหลีกเลี่ยงการตั้งค่าเริ่มต้นนี้


7

ฉันจะเข้าหานี้แตกต่างกัน XACT_ABORT_ONเป็นค้อนเลื่อนคุณสามารถใช้วิธีการกลั่นเพิ่มเติมดูการจัดการข้อยกเว้นและธุรกรรมซ้อน :

create procedure [usp_my_procedure_name]
as
begin
    set nocount on;
    declare @trancount int;
    set @trancount = @@trancount;
    begin try
        if @trancount = 0
            begin transaction
        else
            save transaction usp_my_procedure_name;

        -- Do the actual work here

lbexit:
        if @trancount = 0   
            commit;
    end try
    begin catch
        declare @error int, @message varchar(4000), @xstate int;
        select @error = ERROR_NUMBER(), @message = ERROR_MESSAGE(), @xstate = XACT_STATE();
        if @xstate = -1
            rollback;
        if @xstate = 1 and @trancount = 0
            rollback
        if @xstate = 1 and @trancount > 0
            rollback transaction usp_my_procedure_name;

        raiserror ('usp_my_procedure_name: %d: %s', 16, 1, @error, @message) ;
    end catch   
end
go

วิธีการนี้จะย้อนกลับเมื่อเป็นไปได้เฉพาะงานที่ดำเนินการภายในบล็อก TRY และเรียกคืนสถานะเป็นสถานะก่อนเข้าสู่บล็อก TRY วิธีนี้คุณสามารถทำการประมวลผลที่ซับซ้อนเช่นวนเคอร์เซอร์ซ้ำโดยไม่มีการสูญเสียงานทั้งหมดในกรณีที่เกิดข้อผิดพลาด ข้อเสียเปรียบเพียงข้อเดียวคือเมื่อใช้จุดบันทึกทรานแซคชันคุณจะถูก จำกัด ไม่ให้ใช้สิ่งใดที่เข้ากันไม่ได้กับจุดบันทึกเช่นธุรกรรมแบบกระจาย


ฉันขอขอบคุณที่ตอบกลับของคุณ แต่คำถามคือไม่ได้จริงๆว่าเราควรตั้งXACT_ABORTไปหรือON OFF
Vladimir Baranov

7

TL; DR / บทสรุปสำหรับผู้บริหาร:เกี่ยวกับส่วนนี้ของคำถาม:

ฉันไม่เห็นในสิ่งที่กรณีการควบคุมสามารถส่งผ่านภายในCATCHกับการทำธุรกรรมที่สามารถกระทำเมื่อXACT_ABORTONมีการตั้งค่า

ฉันได้ทำค่อนข้างบิตของการทดสอบเกี่ยวกับเรื่องนี้และตอนนี้ฉันไม่สามารถหากรณีใด ๆ ที่XACT_STATE()ผลตอบแทน1ภายในของCATCHบล็อกเมื่อ@@TRANCOUNT > 0 และเซสชั่นทรัพย์สินของผู้มีXACT_ABORT ONและตามความจริงแล้วตามหน้า MSDN ปัจจุบันของSET XACT_ABORT :

เมื่อ SET XACT_ABORT เปิดอยู่หากคำสั่ง Transact-SQL ทำให้เกิดข้อผิดพลาดรันไทม์ธุรกรรมทั้งหมดจะถูกยกเลิกและย้อนกลับ

คำแถลงนั้นดูเหมือนจะสอดคล้องกับการเก็งกำไรของคุณและการค้นพบของฉัน

บทความเกี่ยวกับ MSDN SET XACT_ABORTมีตัวอย่างเมื่อคำสั่งบางอย่างภายในธุรกรรมดำเนินการสำเร็จและบางอย่างล้มเหลวเมื่อXACT_ABORTตั้งค่าเป็นOFF

จริงแต่ข้อความในตัวอย่างนั้นไม่อยู่ในTRYบล็อก ข้อความเดียวกันเหล่านั้นภายในTRYบล็อกจะยังคงป้องกันการดำเนินการสำหรับข้อความใด ๆ หลังจากข้อความที่ทำให้เกิดข้อผิดพลาด แต่สมมติว่านั่นXACT_ABORTคือOFFเมื่อการควบคุมถูกส่งผ่านไปยังCATCHบล็อกธุรกรรมนั้นยังคงมีผลทางกายภาพในการเปลี่ยนแปลงก่อนหน้านี้ทั้งหมด และสามารถกระทำได้หากนั่นคือความปรารถนาหรือพวกเขาสามารถย้อนกลับ ในทางตรงกันข้ามถ้าXACT_ABORTเป็นONแล้วการเปลี่ยนแปลงใด ๆ ก่อนจะรีดกลับโดยอัตโนมัติและจากนั้นคุณจะได้รับเลือกอย่างใดอย่างหนึ่งดังนี้ก) ปัญหาROLLBACKซึ่งส่วนใหญ่เป็นเพียงการยอมรับสถานการณ์เนื่องจากธุรกรรมถูกย้อนกลับไปแล้วลบด้วยการรีเซ็ต@@TRANCOUNTเป็น0หรือ b) ได้รับข้อผิดพลาด มีตัวเลือกไม่มากใช่ไหม?

หนึ่งรายละเอียดที่สำคัญอาจจะเป็นจิ๊กซอว์ที่ไม่ได้ปรากฏในเอกสารว่าSET XACT_ABORTเป็นว่าทรัพย์สินเซสชั่นนี้และโค้ดตัวอย่างว่าแม้ได้รับรอบตั้งแต่SQL Server 2000 (เอกสารเกือบจะเหมือนกับระหว่างรุ่น) predating TRY...CATCHสร้างซึ่งเป็น นำมาใช้ใน SQL Server 2005 มองไปที่เอกสารนั้นอีกครั้งและกำลังมองหาตัวอย่าง ( โดยไม่ต้องTRY...CATCH ) โดยใช้XACT_ABORT ONทำให้เกิดการทันทีม้วนกลับมาของรายการ: ไม่มีสถานะการทำธุรกรรมของ "uncommittable" (โปรดทราบว่าไม่มีการกล่าวถึงที่ สถานะธุรกรรม "ไม่ยอมรับได้" ทั้งหมดในSET XACT_ABORTเอกสารประกอบนั้น)

ฉันคิดว่ามันสมเหตุสมผลที่จะสรุปว่า:

  1. บทนำของการTRY...CATCHสร้างใน SQL Server 2005 สร้างความต้องการสำหรับสถานะธุรกรรมใหม่ (เช่น "uncommittable") และXACT_STATE()ฟังก์ชันเพื่อรับข้อมูลนั้น
  2. การตรวจสอบXACT_STATE()ในCATCHบล็อกจริง ๆ จะสมเหตุสมผลถ้าทั้งสองอย่างต่อไปนี้เป็นจริง:
    1. XACT_ABORTคือOFF(อื่นXACT_STATE()ควรกลับมา-1และ@@TRANCOUNTจะเป็นสิ่งที่คุณต้องการ)
    2. คุณมีเหตุผลในCATCHบล็อกหรือบางแห่งขึ้นห่วงโซ่ถ้าสายจะซ้อนกันที่ทำให้มีการเปลี่ยนแปลง (กCOMMITหรือแม้กระทั่งดราก้อนใด ๆ DDL ฯลฯ คำสั่ง) แทนการROLLBACKทำ (นี่เป็นกรณีการใช้งานที่ผิดปกติมาก) ** โปรดดูหมายเหตุที่ด้านล่างในส่วน UPDATE 3 เกี่ยวกับคำแนะนำที่ไม่เป็นทางการโดย Microsoft เพื่อตรวจสอบเสมอXACT_STATE()แทนที่จะเป็น@@TRANCOUNTและทำไมการทดสอบแสดงให้เห็นว่าเหตุผลของพวกเขาไม่ดี
  3. บทนำของการTRY...CATCHสร้างใน SQL Server 2005 มีส่วนใหญ่ล้าสมัยXACT_ABORT ONคุณสมบัติเซสชั่นในขณะที่มันให้การควบคุมระดับสูงกว่าการทำธุรกรรม (อย่างน้อยคุณมีตัวเลือกให้COMMITโดยที่XACT_STATE()ไม่ได้กลับมา-1)
    วิธีการดูที่นี้ก็คือก่อนที่จะ SQL Server 2005 , XACT_ABORT ONจัดให้มีวิธีที่ง่ายและเชื่อถือได้ในการประมวลผลหยุดเมื่อมีข้อผิดพลาดเกิดขึ้นเมื่อเทียบกับการตรวจสอบ@@ERRORหลังจากแต่ละคำสั่ง
  4. ตัวอย่างรหัสเอกสารสำหรับXACT_STATE()เป็นที่ผิดพลาดหรือที่ดีที่สุดที่ทำให้เข้าใจผิดในการที่จะแสดงให้เห็นว่าการตรวจสอบXACT_STATE() = 1เมื่อเป็นXACT_ABORTON

ส่วนที่ยาว ;-)

ใช่รหัสตัวอย่างใน MSDN นั้นค่อนข้างสับสน (โปรดดู: @@ TRANCOUNT (Rollback) กับ XACT_STATE ) ;-) และฉันรู้สึกว่ามันทำให้เข้าใจผิดเพราะอาจแสดงบางสิ่งที่ไม่สมเหตุสมผล (ด้วยเหตุผลที่คุณถาม: คุณสามารถมีธุรกรรม "committable" ในCATCHบล็อกเมื่อXACT_ABORTมีON) หรือแม้ว่าจะเป็นไปได้ก็ตาม ยังคงเน้นไปที่ความเป็นไปได้ทางเทคนิคที่มีน้อยคนที่จะต้องการหรือต้องการและไม่สนใจเหตุผลที่มีแนวโน้มว่าจะต้องการ

หากมีข้อผิดพลาดรุนแรงพอในบล็อก TRY ตัวควบคุมจะผ่านเข้าสู่การจับ ดังนั้นถ้าฉันอยู่ใน CATCH ฉันรู้ว่าธุรกรรมมีปัญหาและสิ่งเดียวที่สมเหตุสมผลที่จะทำในกรณีนี้คือการย้อนกลับใช่มั้ย

ฉันคิดว่ามันจะช่วยได้ถ้าเราทำให้แน่ใจว่าเราอยู่ในหน้าเดียวกันเกี่ยวกับความหมายของคำและแนวคิด:

  • "ข้อผิดพลาดรุนแรงพอ":เพื่อให้ชัดเจนTRY ... CATCHจะดักข้อผิดพลาดส่วนใหญ่ รายการสิ่งที่จะไม่ถูกจับปรากฏในหน้า MSDN ที่เชื่อมโยงนั้นภายใต้ส่วน "ข้อผิดพลาดที่ไม่ได้รับผลกระทบจาก TRY … CATCH Construct"

  • "ถ้าฉันอยู่ใน CATCH ฉันรู้ว่าการทำธุรกรรมมีปัญหา" ( เพิ่มphas em ):หากโดย "transaction" คุณหมายถึงหน่วยทางตรรกะของงานตามที่คุณกำหนดโดยการจัดกลุ่มคำสั่งในการทำธุรกรรมอย่างชัดเจนจากนั้น มีโอกาสมากที่สุดใช่ ฉันคิดว่าพวกเราส่วนใหญ่ฐานข้อมูล DB มักจะเห็นด้วยว่าการย้อนกลับคือ "สิ่งที่สมเหตุสมผลเท่านั้นที่จะทำ" เนื่องจากเรามีมุมมองที่คล้ายกันเกี่ยวกับวิธีการและเหตุผลที่เราใช้ธุรกรรมที่ชัดเจนและคิดว่าขั้นตอนใดควรเป็นหน่วยอะตอม ของการทำงาน.

    แต่ถ้าคุณหมายถึงหน่วยงานที่แท้จริงที่ถูกจัดกลุ่มไว้ในธุรกรรมที่ชัดเจนดังนั้นไม่คุณไม่ทราบว่าตัวธุรกรรมนั้นมีปัญหา คุณจะรู้ว่างบดำเนินการในการทำธุรกรรมที่กำหนดไว้อย่างชัดเจนได้ยกข้อผิดพลาด แต่อาจไม่ใช่คำสั่ง DML หรือ DDL และแม้ว่ามันจะเป็นคำสั่ง DML ธุรกรรมเองก็อาจจะยังสามารถตกลงกันได้

จากสองประเด็นที่กล่าวมาข้างต้นเราอาจต้องแยกความแตกต่างระหว่างการทำธุรกรรมที่คุณ "ไม่สามารถ" ยอมรับได้และรายการที่คุณ "ไม่ต้องการ" ที่จะส่งมอบ

เมื่อXACT_STATE()ส่งกลับ1นั่นหมายความว่าธุรกรรมดังกล่าวคือ "committable" ที่คุณมีทางเลือกระหว่างหรือCOMMIT ROLLBACKคุณอาจไม่ต้องการที่จะยอมรับมัน แต่ถ้าสำหรับบางเรื่องที่ยากที่จะเกิดขึ้นด้วยเหตุผลที่คุณต้องการอย่างน้อยคุณก็ทำได้เพราะบางส่วนของการทำธุรกรรมเสร็จสมบูรณ์

แต่เมื่อXACT_STATE()ส่งกลับค่า a -1คุณต้องทำจริง ๆROLLBACKเพราะส่วนหนึ่งของธุรกรรมกลายเป็นสถานะที่ไม่ดี ตอนนี้ฉันเห็นด้วยว่าถ้าการควบคุมได้ถูกส่งไปยัง CATCH block มันก็สมเหตุสมผลแล้วที่จะตรวจสอบ@@TRANCOUNTเพราะแม้ว่าคุณจะสามารถทำธุรกรรมได้ทำไมคุณถึงต้องการ?

แต่ถ้าคุณสังเกตที่ด้านบนของตัวอย่างการตั้งค่าของXACT_ABORT ONการเปลี่ยนแปลงสิ่งเล็กน้อย คุณสามารถมีข้อผิดพลาดปกติหลังจากที่ทำBEGIN TRANว่าจะผ่านการควบคุมไปยังบล็อกที่จับได้เมื่อXACT_ABORTเป็นOFFและ XACT_STATE () 1จะกลับมา แต่ถ้า XACT_ABORT เป็นONแล้วทำรายการคือ "ยกเลิก" (คือทำให้เป็นโมฆะ) สำหรับ 'OL ข้อผิดพลาดใด ๆ แล้วจะกลับมาXACT_STATE() -1ในกรณีนี้ดูเหมือนว่าไร้ประโยชน์ในการตรวจสอบXACT_STATE()ภายในCATCHบล็อกเป็นมันก็ดูเหมือนว่าจะกลับ-1เมื่อเป็นXACT_ABORTON

ดังนั้นแล้วมีXACT_STATE()ไว้เพื่ออะไร เบาะแสบางอย่างคือ:

  • หน้า MSDN สำหรับTRY...CATCHภายใต้ส่วน "ธุรกรรมที่ไม่สามารถทำได้และ XACT_STATE" กล่าวว่า:

    ข้อผิดพลาดที่มักจะจบการทำธุรกรรมนอกบล็อกลองทำธุรกรรมที่จะเข้าสู่สถานะที่ไม่สามารถยอมรับได้เมื่อเกิดข้อผิดพลาดภายในบล็อกลอง

  • หน้า MSDN สำหรับSET XACT_ABORTภายใต้ส่วน "หมายเหตุ" กล่าวว่า:

    เมื่อ SET XACT_ABORT ปิดในบางกรณีเฉพาะคำสั่ง Transact-SQL ที่ยกข้อผิดพลาดจะถูกย้อนกลับและธุรกรรมดำเนินการต่อไป

    และ:

    XACT_ABORT ต้องตั้งค่า ON สำหรับคำสั่งการปรับเปลี่ยนข้อมูลในธุรกรรมโดยนัยหรือชัดเจนกับผู้ให้บริการ OLE DB ส่วนใหญ่รวมถึง SQL Server

  • หน้า MSDN สำหรับBEGIN TRANSACTIONในส่วน "หมายเหตุ" กล่าวว่า:

    ธุรกรรมในพื้นที่ที่เริ่มต้นโดยคำสั่ง BEGIN TRANSACTION จะถูกยกระดับไปยังธุรกรรมแบบกระจายหากมีการดำเนินการต่อไปนี้ก่อนที่คำสั่งจะถูกส่งมอบหรือย้อนกลับ:

    • คำสั่ง INSERT, DELETE หรือ UPDATE ที่อ้างถึงตารางระยะไกลบนเซิร์ฟเวอร์ที่เชื่อมโยงจะถูกดำเนินการ คำสั่ง INSERT, UPDATE หรือ DELETE ล้มเหลวหากผู้ให้บริการ OLE DB ที่ใช้ในการเข้าถึงเซิร์ฟเวอร์ที่เชื่อมโยงไม่รองรับส่วนติดต่อ ITransactionJoin

ดูเหมือนว่าการใช้งานที่เหมาะสมที่สุดจะอยู่ในบริบทของคำสั่ง DML ของเซิร์ฟเวอร์ที่เชื่อมโยง และฉันเชื่อว่าฉันวิ่งเข้าไปในนี้เองเมื่อหลายปีก่อน ฉันจำรายละเอียดทั้งหมดไม่ได้ แต่มีบางอย่างที่เกี่ยวข้องกับเซิร์ฟเวอร์ระยะไกลที่ไม่พร้อมใช้งานและด้วยเหตุผลบางอย่างข้อผิดพลาดนั้นไม่ได้ติดอยู่ในบล็อคลองและไม่เคยถูกส่งไปที่ CATCH เลย COMMIT เมื่อไม่ควรมี แน่นอนว่าอาจเป็นปัญหาของการไม่ได้XACT_ABORTตั้งค่าONแทนที่จะล้มเหลวในการตรวจสอบXACT_STATE()หรืออาจเป็นได้ทั้ง และฉันจำได้ว่ากำลังอ่านสิ่งที่กล่าวว่าถ้าคุณใช้เซิร์ฟเวอร์ที่เชื่อมโยงและ / หรือธุรกรรมที่ถูกแจกจ่ายคุณจำเป็นต้องใช้XACT_ABORT ONและ / หรือXACT_STATE()แต่ฉันไม่สามารถหาเอกสารนั้นได้ในตอนนี้ หากฉันพบฉันจะอัปเดตด้วยลิงค์

แต่ฉันก็ยังมีความพยายามหลายสิ่งหลายอย่างและฉันไม่สามารถหาสถานการณ์ที่มีXACT_ABORT ONและผ่านการควบคุมไปยังCATCHบล็อกที่มีการรายงานXACT_STATE()1

ลองตัวอย่างเหล่านี้เพื่อดูผลกระทบของXACT_ABORTมูลค่าXACT_STATE():

SET XACT_ABORT OFF;

BEGIN TRY
    BEGIN TRAN;

    SELECT 1/0 AS [DivideByZero]; -- error, yo!

    COMMIT TRAN;
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]

    IF (@@TRANCOUNT > 0)
    BEGIN
        ROLLBACK;
    END;
END CATCH;

GO ------------------------------------------------

SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRAN;

    SELECT 1/0 AS [DivideByZero]; -- error, yo!

    COMMIT TRAN;
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]

    IF (@@TRANCOUNT > 0)
    BEGIN
        ROLLBACK;
    END;
END CATCH;

GO ------------------------------------------------

SET XACT_ABORT ON;

BEGIN TRY
    SELECT 1/0 AS [DivideByZero]; -- error, yo!
END TRY
BEGIN CATCH
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            XACT_STATE() AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage]
END CATCH;

UPDATE

แม้ว่าจะไม่ได้เป็นส่วนหนึ่งของคำถามต้นฉบับโดยอ้างอิงจากความคิดเห็นเหล่านี้ในคำตอบนี้:

ฉันได้อ่านผ่านบทความ Erland ในข้อผิดพลาดและการจัดการการทำธุรกรรมที่เขาบอกว่าXACT_ABORTเป็นไปโดยปริยายด้วยเหตุผลเดิมและปกติเราควรจะตั้งค่าให้OFF ... "... ถ้าคุณทำตามคำแนะนำแล้วเปิดด้วย SET XACT_ABORT ON ธุรกรรมจะถูกทำซ้ำ"ON

ก่อนที่จะใช้XACT_ABORT ONทุกที่ฉันจะถามว่า: สิ่งที่ได้รับที่นี่? ฉันไม่พบว่าจำเป็นต้องทำและสนับสนุนโดยทั่วไปว่าคุณควรใช้เมื่อจำเป็นเท่านั้น ไม่ว่าคุณต้องการROLLBACKจะจัดการได้อย่างง่ายดายเพียงพอหรือไม่โดยใช้เทมเพลตที่แสดงในคำตอบของ @ Remus หรือสิ่งที่ฉันใช้มานานหลายปีซึ่งเป็นสิ่งเดียวกัน แต่ไม่มีจุดบันทึกตามที่แสดงในคำตอบนี้ (ซึ่ง จัดการสายซ้อนกัน):

เราจำเป็นต้องจัดการธุรกรรมในรหัส C # หรือในขั้นตอนการจัดเก็บ


อัพเดท 2

ฉันทำการทดสอบเพิ่มอีกเล็กน้อยคราวนี้ด้วยการสร้าง. NET Console แอปขนาดเล็กสร้างธุรกรรมในเลเยอร์แอปก่อนที่จะดำเนินการSqlCommandวัตถุใด ๆ(เช่นผ่านusing (SqlTransaction _Tran = _Connection.BeginTransaction()) { ...) รวมถึงการใช้ข้อผิดพลาดแบบแบทช์แทนคำสั่งเพียงอย่างเดียว ข้อผิดพลาดในการจัดกลุ่มและพบว่า:

  1. ธุรกรรม "ธรรมดา" เป็นรายการที่ถูกย้อนกลับไปแล้วส่วนใหญ่แล้ว (การเปลี่ยนแปลงถูกยกเลิกไปแล้ว) แต่@@TRANCOUNTยังคงเป็น> 0
  2. เมื่อคุณมีการทำธุรกรรม "uncommitable" คุณจะไม่สามารถออกจากการCOMMITที่จะสร้างและข้อผิดพลาดที่บอกว่าการทำธุรกรรมเป็น "uncommittable" คุณยังไม่สามารถเพิกเฉยได้ / ไม่ทำอะไรเลยเนื่องจากข้อผิดพลาดจะถูกสร้างขึ้นเมื่อแบทช์เสร็จสิ้นโดยระบุว่าแบทช์เสร็จสมบูรณ์ด้วยธุรกรรมที่เอ้อระเหยไม่สามารถยอมรับได้และจะถูกย้อนกลับ (ดังนั้นอืมถ้ามันจะย้อนกลับอัตโนมัติ ทำไมต้องทิ้งข้อผิดพลาด?) ดังนั้นคุณต้องออกอย่างชัดเจนROLLBACKอาจไม่ได้อยู่ในCATCHบล็อกทันทีแต่ก่อนที่แบทช์จะสิ้นสุด
  3. ในการTRY...CATCHสร้างเมื่อXACT_ABORTมีOFFข้อผิดพลาดที่จะยุติการทำธุรกรรมโดยอัตโนมัติหากพวกเขาเกิดขึ้นนอกTRYบล็อกเช่นข้อผิดพลาดแบทช์ยกเลิกจะยกเลิกการทำงาน แต่ไม่ยุติ Tranasction ปล่อยให้มันเป็น "uncommitable" การออก a ROLLBACKเป็นวิธีที่จำเป็นมากกว่าในการปิดธุรกรรม แต่งานได้ย้อนกลับไปแล้ว
  4. เมื่อXACT_ABORTใดONข้อผิดพลาดส่วนใหญ่จะทำหน้าที่ยกเลิกการแบทช์และจะทำงานตามที่อธิบายไว้ในหัวข้อย่อยด้านบนโดยตรง (# 3)
  5. XACT_STATE()อย่างน้อยในCATCHบล็อกจะแสดง-1ข้อผิดพลาดสำหรับการยกเลิกแบทช์หากมีธุรกรรมที่ใช้งานอยู่ในเวลาที่เกิดข้อผิดพลาด
  6. XACT_STATE()บางครั้งจะส่งคืน1แม้ว่าจะไม่มีธุรกรรมที่ใช้งานอยู่ ถ้า@@SPID(อื่น) อยู่ในSELECTรายการพร้อมกับXACT_STATE()แล้วXACT_STATE()จะกลับ 1 เมื่อไม่มีการทำธุรกรรมที่ใช้งาน พฤติกรรมนี้เริ่มต้นใน SQL Server 2012 และมีอยู่ในปี 2014 แต่ฉันไม่ได้ทดสอบในปี 2559

ด้วยจุดสำคัญในใจ:

  • จุดที่ได้รับ # 4 และ # 5 เนื่องจากส่วนใหญ่ (หรือทั้งหมด?) ข้อผิดพลาดจะทำให้การทำธุรกรรม "uncommitable" มันดูเหมือนไม่มีจุดหมายทั้งหมดเพื่อตรวจสอบXACT_STATE()ในCATCHบล็อกเมื่อXACT_ABORTเป็นตั้งแต่ค่าที่ส่งกลับจะเป็นON-1
  • การตรวจสอบXACT_STATE()ในCATCHบล็อกเมื่อXACT_ABORTใดที่OFFเหมาะสมกว่าเนื่องจากค่าส่งคืนจะมีการเปลี่ยนแปลงอย่างน้อยเนื่องจากจะส่งคืน1ข้อผิดพลาดการยกเลิกคำสั่ง อย่างไรก็ตามหากคุณเขียนโค้ดเหมือนกับพวกเราส่วนใหญ่ความแตกต่างนี้ไม่มีความหมายเนื่องจากคุณจะโทรROLLBACKไปหาคุณเพียงเพราะความจริงที่ว่ามีข้อผิดพลาดเกิดขึ้น
  • หากคุณพบว่าสถานการณ์ที่ไม่ใบสำคัญแสดงสิทธิที่ออกให้COMMITในCATCHบล็อกแล้วตรวจสอบค่าของและให้แน่ใจว่าXACT_STATE()SET XACT_ABORT OFF;
  • XACT_ABORT ONดูเหมือนว่าจะให้ประโยชน์เล็กน้อยกับไม่มีTRY...CATCHโครงสร้าง
  • ฉันสามารถหาสถานการณ์ที่ไม่มีการตรวจสอบให้เป็นประโยชน์มีความหมายมากกว่าเพียงแค่การตรวจสอบXACT_STATE()@@TRANCOUNT
  • ฉันยังสามารถพบสถานการณ์ที่ไม่มีXACT_STATE()ผลตอบแทน1ในCATCHบล็อกเมื่อเป็นXACT_ABORT ONฉันคิดว่ามันเป็นข้อผิดพลาดของเอกสาร
  • ใช่คุณสามารถย้อนกลับธุรกรรมที่คุณไม่ได้เริ่มต้นอย่างชัดเจน และในบริบทของการใช้XACT_ABORT ONเป็นจุดที่สงสัยเนื่องจากข้อผิดพลาดที่เกิดขึ้นในTRYบล็อกจะย้อนกลับการเปลี่ยนแปลงโดยอัตโนมัติ
  • การTRY...CATCHสร้างมีผลประโยชน์มากกว่าXACT_ABORT ONในการไม่ยกเลิกการทำธุรกรรมทั้งหมดโดยอัตโนมัติดังนั้นจึงอนุญาตให้ทำธุรกรรม (ตราบใดที่มีXACT_STATE()ผลตอบแทน1) ที่จะผูกพัน(แม้ว่าจะเป็นกรณีที่มีขอบ)

ตัวอย่างของการXACT_STATE()กลับมา-1เมื่อXACT_ABORTเป็นOFF:

SET XACT_ABORT OFF;

BEGIN TRY
    BEGIN TRAN;

    SELECT CONVERT(INT, 'g') AS [ConversionError];

    COMMIT TRAN;
END TRY
BEGIN CATCH
    DECLARE @State INT;
    SET @State = XACT_STATE();
    SELECT @@TRANCOUNT AS [@@TRANCOUNT],
            @State AS [XactState],
            ERROR_MESSAGE() AS [ErrorMessage];

    IF (@@TRANCOUNT > 0)
    BEGIN
        SELECT 'Rollin back...' AS [Transaction];
        ROLLBACK;
    END;
END CATCH;

อัพเดท 3

เกี่ยวข้องกับรายการ # 6 ในส่วน UPDATE 2 (เช่นค่าที่เป็นไปได้ที่ไม่ถูกต้องถูกส่งคืนโดยXACT_STATE()เมื่อไม่มีธุรกรรมที่ใช้งานอยู่):

  • ลักษณะการทำงานผิดปกติ / ผิดพลาดเริ่มขึ้นใน SQL Server 2012 (จนถึงขณะนี้ได้รับการทดสอบกับ 2012 SP2 และ 2014 SP1)
  • ใน SQL Server รุ่น 2005, 2008, และ 2008 R2 XACT_STATE()ไม่ได้รายงานค่าที่คาดไว้เมื่อใช้ในทริกเกอร์หรือINSERT...EXECสถานการณ์: xact_state () ไม่สามารถใช้อย่างเชื่อถือได้เพื่อตรวจสอบว่ามีการทำธุรกรรมอีกต่อไปหรือไม่ อย่างไรก็ตามในทั้ง 3 รุ่น (ผมทดสอบเฉพาะใน 2008 R2) XACT_STATE()ไม่ได้ไม่ถูกต้องรายงาน1เมื่อใช้ในด้วยSELECT@@SPID
  • มีข้อผิดพลาดการเชื่อมต่อฟ้องพฤติกรรมที่กล่าวถึงนี่ แต่ถูกปิดเป็น "โดยการออกแบบ": XACT_STATE () สามารถกลับมาเป็นรัฐทำธุรกรรมที่ไม่ถูกต้องใน SQL 2012 อย่างไรก็ตามการทดสอบเสร็จสิ้นเมื่อเลือกจาก DMV และได้ข้อสรุปว่าการทำเช่นนั้นจะมีระบบสร้างธุรกรรมอย่างน้อยที่สุดสำหรับ DMV บางตัว มันก็ยังระบุไว้ในการตอบสนองสุดท้ายโดย MS ว่า:

    โปรดทราบว่าคำสั่ง IF และ SELECT ที่ไม่มีจาก FROM ห้ามเริ่มทำธุรกรรม
    ตัวอย่างเช่นการเรียกใช้ SELECT XACT_STATE () หากคุณไม่มีธุรกรรมที่มีอยู่ก่อนหน้านี้จะคืนค่า 0

    ข้อความเหล่านั้นไม่ถูกต้องตามตัวอย่างต่อไปนี้:

    SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @@SPID AS [SPID];
    GO
    DECLARE @SPID INT;
    SET @SPID = @@SPID;
    SELECT @@TRANCOUNT AS [TRANCOUNT], XACT_STATE() AS [XACT_STATE], @SPID AS [SPID];
    GO
  • ดังนั้นข้อผิดพลาดการเชื่อมต่อใหม่:
    XACT_STATE () ส่งคืน 1 เมื่อใช้ใน SELECT พร้อมกับตัวแปรระบบบางส่วน แต่ไม่มี FROM clause

โปรดทราบว่าใน "XACT_STATE () สามารถส่งคืนสถานะการทำธุรกรรมที่ไม่ถูกต้องใน SQL 2012" รายการเชื่อมต่อที่เชื่อมโยงโดยตรงด้านบนสถานะ Microsoft (ดีตัวแทนของ):

@@ trancount ส่งคืนจำนวนคำสั่ง BEGIN TRAN ดังนั้นจึงไม่ใช่ตัวบ่งชี้ที่เชื่อถือได้ว่ามีธุรกรรมที่ใช้งานอยู่หรือไม่ XACT_STATE () จะส่งคืน 1 หากมีธุรกรรม autocommit ที่ใช้งานอยู่และเป็นตัวบ่งชี้ที่เชื่อถือได้มากขึ้นว่ามีธุรกรรมที่ใช้งานอยู่หรือไม่

อย่างไรก็ตามฉันไม่สามารถหาเหตุผลที่จะไม่ไว้วางใจ@@TRANCOUNTได้ การทดสอบต่อไปนี้แสดงให้เห็นว่า@@TRANCOUNTจะส่งคืน1ในการทำธุรกรรมอัตโนมัติยอมรับ:

--- begin setup
GO
CREATE PROCEDURE #TransactionInfo AS
SET NOCOUNT ON;
SELECT @@TRANCOUNT AS [TranCount],
       XACT_STATE() AS [XactState];
GO
--- end setup

DECLARE @Test TABLE (TranCount INT, XactState INT);

SELECT * FROM @Test; -- no rows

EXEC #TransactionInfo; -- 0 for both fields

INSERT INTO @Test (TranCount, XactState)
    EXEC #TransactionInfo;

SELECT * FROM @Test; -- 1 row; 1 for both fields

ฉันยังทดสอบบนตารางจริงด้วย Trigger และ@@TRANCOUNTภายใน Trigger ได้รายงานอย่างถูกต้อง1แม้ว่าจะไม่มีการทำธุรกรรมที่ชัดเจน


4

การตั้งโปรแกรมการป้องกันต้องให้คุณเขียนโค้ดที่จัดการสถานะที่รู้จักมากที่สุดเท่าที่จะเป็นไปได้ซึ่งจะช่วยลดความเป็นไปได้ของข้อบกพร่อง

การตรวจสอบ XACT_STATE () เพื่อตรวจสอบว่าสามารถทำการย้อนกลับได้หรือไม่นั้นเป็นวิธีปฏิบัติที่ดี การพยายามย้อนกลับแบบสุ่มหมายถึงคุณอาจทำให้เกิดข้อผิดพลาดภายใน TRY ... CATCH ของคุณ

วิธีหนึ่งในการย้อนกลับอาจล้มเหลวภายใน TRY ... CATCH จะเป็นถ้าคุณไม่ได้เริ่มทำธุรกรรมอย่างชัดเจน การคัดลอกและวางบล็อคโค้ดอาจทำให้เกิดปัญหานี้ได้ง่าย


ขอบคุณที่ตอบ. ฉันไม่สามารถนึกถึงกรณีที่ความเรียบง่ายROLLBACKจะไม่ทำงานภายในCATCHและคุณได้เป็นตัวอย่างที่ดี ฉันเดาว่ามันอาจกลายเป็นยุ่งได้อย่างรวดเร็วหากการทำธุรกรรมซ้อนและขั้นตอนการจัดเก็บซ้อนกับพวกเขาTRY ... CATCH ... ROLLBACKมีส่วนเกี่ยวข้อง
Vladimir Baranov

ถึงกระนั้นฉันขอขอบคุณถ้าคุณสามารถขยายคำตอบของคุณเกี่ยวกับส่วนที่สอง - IF (XACT_STATE()) = 1 COMMIT TRANSACTION; เราจะจบลงCATCHด้วยการทำธุรกรรมที่ตกลงกันได้อย่างไร ฉันจะไม่กล้าที่จะกระทำบางคน (เป็นไปได้) CATCHขยะจากภายใน เหตุผลของฉันคือถ้าเราอยู่ในCATCHสิ่งที่ผิดพลาดฉันไม่สามารถเชื่อถือสถานะของฐานข้อมูลROLLBACKได้
Vladimir Baranov
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.