Tại sao Sql Server tiếp tục thực thi sau khi raiserror khi xact_abort được bật?


87

Tôi chỉ ngạc nhiên bởi một cái gì đó trong TSQL. Tôi nghĩ rằng nếu xact_abort được bật, gọi một cái gì đó như

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

sẽ ngừng thực hiện thủ tục đã lưu trữ (hoặc bất kỳ lô nào).

Nhưng thông báo lỗi ADO.NET của tôi đã chứng minh điều ngược lại. Tôi nhận được cả thông báo lỗi raiserror trong thông báo ngoại lệ, cộng với điều tiếp theo đã xảy ra sau đó.

Đây là cách giải quyết của tôi (dù sao cũng là thói quen của tôi), nhưng có vẻ như nó không cần thiết:

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

Các tài liệu nói điều này:

Khi SET XACT_ABORT được BẬT, nếu một câu lệnh Transact-SQL phát sinh lỗi thời gian chạy, toàn bộ giao dịch sẽ bị chấm dứt và quay trở lại.

Điều đó có nghĩa là tôi phải sử dụng một giao dịch rõ ràng?


Vừa mới được kiểm tra và RAISERRORtrên thực tế sẽ chấm dứt thực thi nếu mức độ nghiêm trọng được đặt thành 17 hoặc 18, thay vì 16
được cải cách vào

2
Chỉ cần kiểm tra SQL Server 2012 và RAISERRORtrong thực tế sẽ không chấm dứt thực hiện nếu mức độ nghiêm trọng được thiết lập để 17 hoặc 18, thay vì 16.
Ian Boyd

1
Lý do được giải thích rõ ràng bởi Erland Sommarskog (SQL Server MVP từ năm 2001) trong phần 2 của loạt bài xuất sắc về Xử lý Giao dịch và Lỗi trong SQL Server: "Thỉnh thoảng, tôi có cảm giác rằng SQL Server được thiết kế cố ý để khó hiểu nhất có thể. Khi họ lên kế hoạch cho một bản phát hành mới, họ hỏi nhau rằng chúng ta có thể làm gì vào thời điểm này để khiến người dùng bối rối? Đôi khi họ chạy ra một chút ý tưởng, nhưng sau đó ai đó nói hãy làm gì đó với việc xử lý lỗi! "
Kỹ sư đảo ngược

@IanBoyd, thực sự, ngay cả sau khi đặt mức độ nghiêm trọng thành 17 hoặc 18 hoặc 19, việc thực thi vẫn không dừng lại. Điều thú vị hơn, nếu bạn nhìn vào Messagestab bạn sẽ thấy không có (X rows affected)hoặc PRINTtin nhắn, mà tôi sẽ nói là hoàn toàn dối trá !
Gabrielius

Câu trả lời:


48

Đây là By Design TM , như bạn có thể thấy trên Connect by SQL Server phản hồi của nhóm SQL Server cho một câu hỏi tương tự:

Cảm ơn phản hôi của bạn. Theo thiết kế, tùy chọn bộ XACT_ABORT không ảnh hưởng đến hoạt động của câu lệnh RAISERROR. Chúng tôi sẽ xem xét phản hồi của bạn để sửa đổi hành vi này cho bản phát hành SQL Server trong tương lai.

Có, đây là một chút vấn đề đối với một số người hy vọng RAISERRORvới mức độ nghiêm trọng cao (như 16) sẽ giống như lỗi thực thi SQL - không phải vậy.

Cách giải quyết của bạn chỉ là về những gì bạn cần làm và việc sử dụng một giao dịch rõ ràng không có bất kỳ ảnh hưởng nào đến hành vi bạn muốn thay đổi.


1
Cảm ơn Philip. Liên kết bạn đã tham chiếu dường như không có sẵn.
Eric Z Beard

2
Liên kết đang làm việc tốt, nếu bao giờ bạn cần phải tìm kiếm nó, tựa đề "Have RAISERROR làm việc với XACT_ABORT", tác giả "jorundur", ID: 275.308
JohnC

Liên kết đã chết, không có bản sao lưu trong bộ nhớ cache archive.org. Nó đã bị mất vào cát của thời gian mãi mãi.
Ian Boyd

Câu trả lời này là một bản sao lưu tốt - với một liên kết đến các tài liệu nơi hành vi này được thể hiện rõ ràng.
pcdev

25

Nếu bạn sử dụng khối thử / bắt, một số lỗi raiserror với mức độ nghiêm trọng 11-19 sẽ khiến việc thực thi chuyển sang khối bắt.

Bất kỳ mức độ nghiêm trọng nào trên 16 đều là lỗi hệ thống. Để chứng minh đoạn mã sau, hãy thiết lập một khối try / catch và thực thi một thủ tục đã lưu trữ mà chúng tôi cho rằng sẽ không thành công:

giả sử chúng ta có một bảng [dbo]. [Lỗi] giữ lỗi giả sử chúng ta có một thủ tục được lưu trữ [dbo]. [AssumeThisFails] sẽ không thành công khi chúng ta thực thi nó

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

Sử dụng RETURNngay sau RAISERROR()đó và nó sẽ không thực thi quy trình nữa.


8
Bạn có thể muốn gọi rollback transactiontrước khi gọi return.
Mike Christian

1
Có lẽ bạn có thể cần phải làm điều gì đó trong khối catch của bạn
sqluser

14

Như đã chỉ ra trên tài liệu cho SET XACT_ABORT, THROWcâu lệnh nên được sử dụng thay vì RAISERROR.

Hai hành vi hơi khác nhau . Nhưng khi XACT_ABORTđược đặt thành BẬT, thì bạn nên luôn sử dụng THROWlệnh.


25
Nếu bạn không có 2k12 (hoặc cao hơn khi nó xuất hiện), thì không có câu lệnh THROW nào được sử dụng.
Jeff Moden
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.