2010-01-24 13 views
17

¿Crees que hay una forma mejor de escribir una transacción en t-sql? ¿Existe un mejor enfoque que mejore el mantenimiento y el rendimiento de la aplicación que utiliza esta transacción?escribiendo una transacción en t-sql y manejo de errores

-- Description: Insert email Receiver under specified subject 
-- ============================================= 
ALTER PROCEDURE [Contact].[Receiver_stpInsert] 
    @First_Name nvarchar(30), 
    @Last_Name nvarchar(30), 
    @Email varchar(60), 
    @Subject_Id int 
AS 
BEGIN 
    SET NOCOUNT ON; 

    DECLARE @error_num int; 


    BEGIN TRANSACTION 

    INSERT INTO Contact.Receiver(First_Name, Last_Name, Email) VALUES(@First_Name, @Last_Name, @Email); 

    SET @error_num = @@ERROR; 
    IF (@error_num <> 0) 
     BEGIN 
      ROLLBACK; 
      RETURN; 
     END 

    DECLARE @rec_record_id int; 
    SET @rec_record_id = (SELECT Record_Id FROM Contact.Receiver WHERE Email = @Email); 

    SET @error_num = @@ERROR; 
    IF (@error_num <> 0) 
     BEGIN 
      ROLLBACK; 
      RETURN; 
     END 

    INSERT INTO Contact.Receiver_Subject(Receiver_Id, Subject_Id) VALUES(@rec_record_id, @Subject_Id); 

    SET @error_num = @@ERROR; 
    IF (@error_num <> 0) 
     BEGIN 
      ROLLBACK; 
      RETURN; 
     END 

    SET @error_num = @@ERROR; 
    IF (@error_num <> 0) 
     BEGIN 
      ROLLBACK; 
      RETURN; 
     END 
    ELSE 
     BEGIN 
      Commit; 

     END 

END 

Respuesta

33

Si está utilizando SQL 2005 o posterior, puede utilizar el bloque TRY...CATCH, así:

BEGIN TRY 
    BEGIN TRANSACTION; 

    INSERT INTO Contact.Receiver(First_Name, Last_Name, Email) VALUES (@First_Name, @Last_Name, @Email); 
    ... other inserts etc 
    ... 
    COMMIT TRANSACTION; 
END TRY 
BEGIN CATCH 
    IF @@TRANCOUNT > 0 
     ROLLBACK TRANSACTION; 
END CATCH; 

De esta manera, no se mantienen repitiendo los mismos bloques de código de comprobación de @ @ERROR. Si usted quiere saber lo que ha producido un error en el bloque CATCH COMENZAR puede obtener varios bits de información:

  • ERROR_NUMBER() devuelve el número del error.
  • ERROR_SEVERITY() devuelve la gravedad.
  • ERROR_STATE() devuelve el número de estado del error.
  • ERROR_PROCEDURE() devuelve el nombre del procedimiento almacenado o dispara donde ocurrió el error.
  • ERROR_LINE() devuelve el número de línea dentro de la rutina que provocó el error .
  • ERROR_MESSAGE() devuelve el texto completo del mensaje de error. El texto incluye los valores suministrados para cualquier parámetros sustituibles, como longitudes, nombres de objeto o horas.
+13

Pondría COMMIT TRANSACTION en el bloque BEGIN TRY .... END TRY, no después de toda la afirmación. ¿No sería eso más fácil y más preciso? –

+0

¿por qué no poner 'BEGIN TRANSACTION' después del' BEGIN TRY' también? –

+0

Esto fue hace 6 años ... no recuerdo bien lo que estaba pensando;) Pero sí, pondría la transacción dentro de BEGIN TRY. He actualizado la respuesta. – AdaTheDev

5

Si tiene SQL Server 2000 o antes, entonces sí - la comprobación del valor @@ERROR es básicamente todo lo que puede hacer.

Con SQL Server 2005, Microsoft introdujo la construcción TRY ... CATCH que hace que sea mucho más fácil:

BEGIN TRY 
    ...... 
    -- your T-SQL code here 
    ...... 
END TRY 
BEGIN CATCH 
    SELECT 
     ERROR_NUMBER() AS ErrorNumber, 
     ERROR_SEVERITY() AS ErrorSeverity, 
     ERROR_STATE() AS ErrorState, 
     ERROR_PROCEDURE() AS ErrorProcedure, 
     ERROR_LINE() AS ErrorLine, 
     ERROR_MESSAGE() AS ErrorMessage 

    -- do other steps, if you want 
END CATCH 
2

Si está utilizando SQL 2005 o superior debe tener en cuenta el enfoque TRY CATCH

2

Puede envolverlo todo en una captura de prueba, y luego solo necesita codificar la reversión en un solo lugar. Ver this para más detalles.

4

Preguntado no hace mucho tiempo. My answer con una plantilla TRY/CATCH

10

Durante mucho tiempo he estado defendiendo el uso de TRY/CATCH and nested transactions in stored procedures.

Este patrón se da no sólo el manejo de errores muy simplificado del bloque try/catch en comparación con el @@ comprobación de errores, pero también da todo-o-nada anidado semántica para invocaciones de procedimientos.

Si se invoca el procedimiento en el contexto de una transacción, el procedimiento retrotrae solo sus propios cambios y deja al interlocutor para decidir si restituye la transacción de incrustación o intenta una ruta de error alternativa.

create procedure [usp_my_procedure_name] 
as 
begin 
    set nocount on; 
    declare @trancount int; 
    set @trancount = @@trancount; 
    begin try 
     if @trancount = 0 
      begin transaction 
     else 
      save transaction usp_my_procedure_name; 

     -- Do the actual work here 

lbexit: 
     if @trancount = 0 
      commit; 
    end try 
    begin catch 
     declare @error int, @message varchar(4000), @xstate int; 
     select @error = ERROR_NUMBER(), @message = ERROR_MESSAGE(), @xstate = XACT_STATE(); 
     if @xstate = -1 
      rollback; 
     if @xstate = 1 and @trancount = 0 
      rollback 
     if @xstate = 1 and @trancount > 0 
      rollback transaction usp_my_procedure_name; 

     raiserror ('usp_my_procedure_name: %d: %s', 16, 1, @error, @message) ; 
     return; 
    end catch 
end 

Las pegas de este enfoque son:

  • lo hace no funciona con transacciones distribuidas. Debido a que los puntos de almacenamiento de transacciones son incompatibles con las transacciones distribuidas, no puede usar este patrón cuando se requieren transacciones distribuidas. Las transacciones distribuidas en mi humilde opinión son malas y nunca se deben usar de todos modos.
  • Es altera el error original. Este problema es inherente a los bloques TRY/CATCH y no hay nada que puedas hacer al respecto. Una aplicación que esté preparada para tratar con los códigos de error del Servidor SQL original (como 1202, 1205, 2627, etc.) tendrá que cambiarse para tratar los códigos de error en el rango anterior de 50000 generado por el código Transact-SQL que usa TRY/CATCH .

También hay que tener cuidado con el uso de SET XACT_ABORT ON. Esta configuración hará que un lote aborte una transacción con cualquier error. Eso hace que cualquier transacción de TRY/CATCH sea básicamente inútil y recomiendo evitarlo.

+0

El tercer inconveniente sería la excesiva copia y pegado ... realmente desearía que hubiera una manera de evitar toda la repetición. – Aaronaught

+1

@Aaronaught: Desafortunadamente ese es el estado del arte con respecto a Transact-SQL. Simplemente no es un lenguaje amigable para la reutilización del código y la brevedad. Generar el código a través de herramientas (por ejemplo, XSTL + XML) ayuda en gran medida a resolver ese problema eliminando la naturaleza repetitiva y propensa a errores de escribir T-SQL. –