it-swarm-es.tech

¿Por qué Sql Server sigue ejecutándose después de raiserror cuando xact_abort está activado?

Acabo de sorprenderme por algo en TSQL. Pensé que si xact_abort estaba encendido, llamando a algo como

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

detendría la ejecución del procedimiento almacenado (o cualquier lote).

Pero mi mensaje de error ADO.NET acaba de demostrar lo contrario. Obtuve tanto el mensaje de error raiserror en el mensaje de excepción, más la siguiente cosa que se rompió después de eso.

Esta es mi solución (que es mi hábito de todos modos), pero no parece que deba ser necesaria:

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

Los doctores dicen esto:

Cuando SET XACT_ABORT está ENCENDIDO, si una instrucción Transact-SQL genera un error en tiempo de ejecución, la transacción completa se termina y se revierte.

¿Eso significa que debo estar usando una transacción explícita?

84
Eric Z Beard

Esto es por diseñoTM, como puede ver en Connect por la respuesta del equipo de SQL Server a una pregunta similar:

Gracias por tus comentarios. Por diseño, la opción de establecer XACT_ABORT no afecta el comportamiento de la declaración RAISERROR. Consideraremos sus comentarios para modificar este comportamiento para una futura versión de SQL Server.

Sí, esto es un problema para algunos que esperaban que RAISERROR con una severidad alta (como 16) fuera lo mismo que un error de ejecución de SQL, no lo es.

Su solución es solo lo que necesita hacer, y el uso de una transacción explícita no tiene ningún efecto en el comportamiento que desea cambiar.

45
Philip Rieck

Si utiliza un bloque try/catch, un número de error de raiserror con gravedad 11-19 hará que la ejecución salte al bloque catch.

Cualquier gravedad por encima de 16 es un error del sistema. Para demostrar el siguiente código, configure un bloque try/catch y ejecute un procedimiento almacenado que asumimos que fallará:

supongamos que tenemos una tabla [dbo]. [Errores] para contener errores, asumimos que tenemos un procedimiento almacenado [dbo]. [AssumeThisFails] que fallará cuando lo ejecutemos

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

Use RETURN inmediatamente después de RAISERROR() y no se ejecutará más el procedimiento.

22
piyush

Como se señaló en MSDN la instrucción THROW debe usarse en lugar de RAISERROR.

Los dos se comportan ligeramente diferente . Pero cuando XACT_ABORT se establece en ON, entonces siempre debe usar el comando THROW.

12
Möoz