it-swarm-vi.tech

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

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, hãy gọi một cái gì đó như

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

sẽ dừng thực thi thủ tục đượ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ỉ 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 đã phá vỡ 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ư 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 câu lệnh Transact-SQL xuất hiện lỗi thời gian chạy, toàn bộ giao dịch bị chấm dứt và được khôi phục.

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

84
Eric Z Beard

Đây là theo thiết kếTM, như bạn có thể thấy trên Kết nối bởi câu trả lờ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 hành vi 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 để phát hành SQL Server trong tương lai.

Có, đây là một vấn đề đối với một số người hy vọng RAISERROR vớ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à 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.

45
Philip Rieck

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

Bất kỳ mức độ nghiêm trọng trên 16 là một lỗi hệ thống. Để chứng minh đoạn mã sau thiết lập khối thử/bắt và thực hiện quy trình được lưu trữ mà chúng tôi giả sử sẽ thất bại:

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]. [Giả sử ThisFails] sẽ thất bại 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
23
ninegrid

Sử dụng RETURN ngay sau RAISERROR() và nó sẽ không thực hiện thủ tục thêm nữa.

22
piyush

Như đã chỉ ra trên MSDN nên sử dụng câu lệnh THROW thay vì RAISERROR.

Hai người cư xử hơi khác nha . Nhưng khi XACT_ABORT được đặt thành BẬT, sau đó bạn phải luôn sử dụng lệnh THROW.

13
Möoz