เหตุใด Sql Server จึงยังคงทำงานหลังจาก raiserror เมื่อ xact_abort เปิดอยู่


88

ฉันรู้สึกประหลาดใจกับบางสิ่งใน TSQL ฉันคิดว่าถ้าเปิด xact_abort จะเรียกอะไรแบบนี้

raiserror('Something bad happened', 16, 1);

จะหยุดการดำเนินการของกระบวนงานที่เก็บไว้ (หรือชุดใด ๆ )

แต่ข้อความแสดงข้อผิดพลาด ADO.NET ของฉันพิสูจน์แล้วว่าตรงกันข้าม ฉันได้รับทั้งข้อความแสดงข้อผิดพลาด raiserror ในข้อความข้อยกเว้นรวมถึงสิ่งถัดไปที่พังหลังจากนั้น

นี่เป็นวิธีแก้ปัญหาของฉัน (ซึ่งก็เป็นนิสัยของฉันอยู่ดี) แต่ดูเหมือนว่ามันจะไม่จำเป็น:

if @somethingBadHappened
    begin;
        raiserror('Something bad happened', 16, 1);
        return;
    end;

เอกสารกล่าวว่า:

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

หมายความว่าฉันต้องใช้ธุรกรรมที่โจ่งแจ้งหรือไม่?


เพิ่งทดสอบและRAISERRORในความเป็นจริงจะยุติการดำเนินการหากตั้งค่าความรุนแรงเป็น 17 หรือ 18 แทนที่จะเป็น 16
ปฏิรูป

2
เพิ่งทดสอบ SQL Server 2012 และRAISERRORในความเป็นจริงจะไม่ยุติการดำเนินการหากตั้งค่าความรุนแรงเป็น 17 หรือ 18 แทนที่จะเป็น 16
Ian Boyd

1
เหตุผลได้รับการอธิบายอย่างชัดเจนโดยErland Sommarskog (SQL Server MVP ตั้งแต่ปี 2544) ในส่วนที่ 2ของชุดข้อผิดพลาดและการจัดการธุรกรรมที่ยอดเยี่ยมใน SQL Server: "ทุกๆครั้งฉันรู้สึกว่า SQL Server ได้รับการออกแบบโดยเจตนาให้เป็น สับสนเป็นที่สุดเมื่อพวกเขาวางแผนสำหรับการเปิดตัวรุ่นใหม่พวกเขาถามกันและกันว่าคราวนี้เราจะทำอะไรได้บ้างเพื่อให้ผู้ใช้สับสนบางครั้งพวกเขาใช้ความคิดเล็กน้อย แต่ก็มีคนบอกว่ามาทำอะไรบางอย่างโดยจัดการข้อผิดพลาดกันเถอะ! "
Reversed Engineer

1
@IanBoyd แน่นอนแม้หลังจากตั้งค่าความรุนแรงเป็น 17 หรือ 18 หรือ 19 การดำเนินการก็ยังไม่หยุด สิ่งที่น่าสนใจกว่านั้นคือถ้าคุณดูในMessagesแท็บคุณจะไม่เห็นข้อความ(X rows affected)หรือPRINTข้อความซึ่งฉันจะบอกว่ามันเป็นเรื่องโกหก !
Gabrielius

คำตอบ:


48

นี่คือ By Design TMดังที่คุณเห็นบนConnectโดยคำตอบของทีม SQL Server สำหรับคำถามที่คล้ายกัน:

ขอบคุณสำหรับความคิดเห็นของคุณ. ตามการออกแบบชุดตัวเลือก XACT_ABORT จะไม่ส่งผลต่อลักษณะการทำงานของคำสั่ง RAISERROR เราจะพิจารณาคำติชมของคุณเพื่อปรับเปลี่ยนพฤติกรรมนี้สำหรับ SQL Server ในอนาคต

ใช่นี่เป็นปัญหาเล็กน้อยสำหรับบางคนที่หวังว่าจะRAISERRORมีความรุนแรงสูง (เช่น16) จะเหมือนกับข้อผิดพลาดในการดำเนินการ SQL - ไม่ใช่

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


1
ขอบคุณ Philip ดูเหมือนว่าลิงก์ที่คุณอ้างถึงจะไม่สามารถใช้งานได้
Eric Z Beard

2
ลิงก์ใช้งานได้ดีหากคุณต้องการค้นหาให้ใช้ชื่อ "ให้ RAISERROR ทำงานกับ XACT_ABORT" ผู้แต่ง "jorundur", ID: 275308
JohnC

ลิงก์หมดแล้วโดยไม่มีสำเนาแคชของ archive.org มันสูญหายไปกับทรายแห่งกาลเวลาตลอดกาล
Ian Boyd

คำตอบนี้เป็นข้อมูลสำรองที่ดีพร้อมลิงก์ไปยังเอกสารซึ่งทำให้พฤติกรรมนี้ชัดเจน
pcdev

25

หากคุณใช้บล็อก try / catch หมายเลขข้อผิดพลาด raiserror ที่มีความรุนแรง 11-19 จะทำให้การดำเนินการข้ามไปที่บล็อก catch

ความรุนแรงใด ๆ ที่สูงกว่า 16 ถือเป็นข้อผิดพลาดของระบบ เพื่อสาธิตรหัสต่อไปนี้ตั้งค่าบล็อก try / catch และดำเนินการตามขั้นตอนการจัดเก็บที่เราถือว่าล้มเหลว:

สมมติว่าเรามีตาราง [dbo] [ข้อผิดพลาด] เพื่อเก็บข้อผิดพลาดสมมติว่าเรามีกระบวนงานที่เก็บไว้ [dbo] [AssumeThisFails] ซึ่งจะล้มเหลวเมื่อเราดำเนินการ

-- first lets build a temporary table to hold errors
if (object_id('tempdb..#RAISERRORS') is null)
 create table #RAISERRORS (ErrorNumber int, ErrorMessage varchar(400), ErrorSeverity int, ErrorState int, ErrorLine int, ErrorProcedure varchar(128));

-- this will determine if the transaction level of the query to programatically determine if we need to begin a new transaction or create a save point to rollback to
declare @tc as int;
set @tc = @@trancount;
if (@tc = 0)
 begin transaction;
else
 save transaction myTransaction;

-- the code in the try block will be executed
begin try
 declare @return_value = '0';
 set @return_value = '0';
 declare
  @ErrorNumber as int,
  @ErrorMessage as varchar(400),
  @ErrorSeverity as int,
  @ErrorState as int,
  @ErrorLine as int,
  @ErrorProcedure as varchar(128);


 -- assume that this procedure fails...
 exec @return_value = [dbo].[AssumeThisFails]
 if (@return_value <> 0)
  raiserror('This is my error message', 17, 1);

 -- the error severity of 17 will be considered a system error execution of this query will skip the following statements and resume at the begin catch block
 if (@tc = 0)
  commit transaction;
 return(0);
end try


-- the code in the catch block will be executed on raiserror("message", 17, 1)
begin catch
  select
   @ErrorNumber = ERROR_NUMBER(),
   @ErrorMessage = ERROR_MESSAGE(),
   @ErrorSeverity = ERROR_SEVERITY(),
   @ErrorState = ERROR_STATE(),
   @ErrorLine = ERROR_LINE(),
   @ErrorProcedure = ERROR_PROCEDURE();

  insert #RAISERRORS (ErrorNumber, ErrorMessage, ErrorSeverity, ErrorState, ErrorLine, ErrorProcedure)
   values (@ErrorNumber, @ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorLine, @ErrorProcedure);

  -- if i started the transaction
  if (@tc = 0)
  begin
   if (XACT_STATE() <> 0)
   begin
     select * from #RAISERRORS;
    rollback transaction;
    insert into [dbo].[Errors] (ErrorNumber, ErrorMessage, ErrorSeverity, ErrorState, ErrorLine, ErrorProcedure)
     select * from #RAISERRORS;
    insert [dbo].[Errors] (ErrorNumber, ErrorMessage, ErrorSeverity, ErrorState, ErrorLine, ErrorProcedure)
     values (@ErrorNumber, @ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorLine, @ErrorProcedure);
    return(1);
   end
  end
  -- if i didn't start the transaction
  if (XACT_STATE() = 1)
  begin
   rollback transaction myTransaction;
   if (object_id('tempdb..#RAISERRORS') is not null)
    insert #RAISERRORS (ErrorNumber, ErrorMessage, ErrorSeverity, ErrorState, ErrorLine, ErrorProcedure)
     values (@ErrorNumber, @ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorLine, @ErrorProcedure);
   else
    raiserror(@ErrorMessage, @ErrorSeverity, @ErrorState);
   return(2); 
  end
  else if (XACT_STATE() = -1)
  begin
   rollback transaction;
   if (object_id('tempdb..#RAISERRORS') is not null)
    insert #RAISERRORS (ErrorNumber, ErrorMessage, ErrorSeverity, ErrorState, ErrorLine, ErrorProcedure)
     values (@ErrorNumber, @ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorLine, @ErrorProcedure);
   else
    raiserror(@ErrorMessage, @ErrorSeverity, @ErrorState);
   return(3);
  end
 end catch
end

22

ใช้RETURNทันทีหลังจากRAISERROR()นั้นจะไม่ดำเนินการตามขั้นตอนต่อไป


8
คุณอาจต้องการที่จะเรียกก่อนที่จะเรียกrollback transaction return
Mike Christian

1
คุณอาจต้องทำอะไรบางอย่างในบล็อกของคุณ
sqluser

15

เป็นแหลมออกบนเอกสารสำหรับSET XACT_ABORTการคำสั่งควรจะนำมาใช้แทนTHROWRAISERROR

ทั้งสองประพฤติแตกต่างกันเล็กน้อย แต่เมื่อXACT_ABORTใดที่ตั้งค่าเป็นเปิดคุณควรใช้THROWคำสั่งเสมอ


25
หากคุณไม่มี 2k12 (หรือสูงกว่าเมื่อออกมา) แสดงว่าไม่มีคำสั่ง THROW
Jeff Moden
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.