2008-09-16 15 views
77

Me acaba de sorprender algo en TSQL. Pensé que si estaba en XACT_ABORT, llamando algo así como¿Por qué Sql Server se sigue ejecutando después de raiserror cuando xact_abort está activado?

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

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

Pero mi mensaje de error ADO.NET acaba de demostrar lo contrario. Recibí tanto el mensaje de error de error del mensaje de excepción, como el siguiente mensaje que se rompió después de eso.

Esta es mi solución (que es mi costumbre de todos modos), pero no parece que debería ser necesario:

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

Los documentos dicen que esto:

Cuando SET es XACT_ABORT ON, si una instrucción de Transact-SQL genera un error de tiempo de ejecución, la transacción completa se cancela y se retrotrae.

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

+0

Just tested y 'RAISERROR' de hecho terminará la ejecución si la gravedad se establece en 17 o 18, en lugar de 16. – reformed

Respuesta

43

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

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

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

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

+0

Gracias Philip. El enlace al que hizo referencia parece no estar disponible. –

+2

El enlace está funcionando bien, si alguna vez necesita buscarlo, título "Haga que RAISERROR trabaje con XACT_ABORT", autor "jorundur", ID: 275308 – JohnC

22

Si usa un bloque de prueba/captura, un error de número de error con severidad 11-19 hará que la ejecución salte al bloque catch.

Cualquier gravedad superior a 16 es un error del sistema. Para demostrar el siguiente código configura un bloque try/catch y ejecuta un procedimiento almacenado que suponemos que fallará:

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

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

uso RETURN inmediatamente después de RAISERROR() y no vamos a ejecutar el procedimiento ulterior.

+8

Es posible que desee llamar a 'rollback transaction' antes de llamar' return'. –

+1

Probablemente deba hacer algo en su bloque catch – sqluser

12

Como se señaló en MSDN se debe utilizar la declaración THROW en lugar de RAISERROR.

Los dos se comportan slightly differently. Pero cuando XACT_ABORT está activado, entonces siempre debe usar el comando THROW.

+24

Si no tiene 2k12 (o más cuando sale), entonces no hay ninguna instrucción THROW. –

Cuestiones relacionadas