Pregunta

Tengo un procedimiento almacenado que necesita establecer un punto de guardado para que pueda, bajo ciertas circunstancias, deshacer todo lo que hizo y devolver un código de error a la persona que llama, o aceptarlo/confirmarlo y devolver el éxito a la persona que llama.Pero necesito que funcione independientemente de que la persona que llama ya haya iniciado una transacción o no.El documento es extremadamente confuso sobre este tema.Esto es lo que creo que funcionará, pero no estoy seguro de todas las ramificaciones.

La cosa es - esto Stored Procedure (SP) es llamado por otros.Entonces no sé si han iniciado una transacción o no...Incluso si pido a los usuarios que inicien una transacción para usar mi SP, todavía tengo preguntas sobre el uso adecuado de Save Points ...

Mi SP probará si hay una transacción en progreso y, si no, comenzará una con BEGIN TRANSACTION.Si una transacción ya está en progreso, se creará un punto de guardado con SAVE TRANSACTION MySavePointName, y guarde el hecho de que esto es lo que hice.

Entonces, si tengo que revertir mis cambios, si hice un BEGIN TRANSACTION antes, entonces lo haré ROLLBACK TRANSACTION.Si hice el punto de guardado, entonces lo haré ROLLBACK TRANSACTION MySavePointName.Este escenario parece funcionar muy bien.

Aquí es donde me confundo un poco: si quiero conservar el trabajo que he realizado, si comencé una transacción, la ejecutaré. COMMIT TRANSACTION.¿Pero si creé un punto de guardado?Lo intenté COMMIT TRANSACTION MySavePointName, pero luego la persona que llama intenta confirmar su transacción y obtiene un error:

La solicitud COMMIT TRANSACTION no tiene BEGIN TRANSACTION correspondiente.

Entonces me pregunto: se puede revertir un punto de guardado (eso funciona: ROLLBACK TRANSACTION MySavePointName NO revertirá la transacción de la persona que llama).¿Pero quizás nunca sea necesario "cometerlo"?¿Simplemente permanece allí, en caso de que necesite volver a él, pero desaparece una vez que se confirma (o revierte) la transacción original?

Si existe una forma "mejor" de "anidar" una transacción, arroje algo de luz también.No he descubierto cómo anidar BEGIN TRANSACTION pero solo revertir o confirmar mi transacción interna.Parece ROLLBACK siempre volverá a la transacción superior, mientras que COMMIT simplemente disminuye @@trancount.

¿Fue útil?

Solución

Creo que ya he descubierto todo esto, así que responderé mi propia pregunta...

Incluso escribí mis hallazgos en un blog si quieres más detalles en http://geekswithblogs.net/bbiales/archive/2012/03/15/how-to-nest-transactions-nicely---quotbegin-transactionquot-vs-quotsave.aspx

Entonces mi SP comienza con algo como esto, para iniciar una nueva transacción si no hay ninguna, pero usar un Punto de guardado si ya hay una en progreso:

DECLARE @startingTranCount int
SET @startingTranCount = @@TRANCOUNT

IF @startingTranCount > 0
    SAVE TRANSACTION mySavePointName
ELSE
    BEGIN TRANSACTION
-- …

Luego, cuando esté listo para confirmar los cambios, solo necesita confirmar si iniciamos la transacción nosotros mismos:

IF @startingTranCount = 0
    COMMIT TRANSACTION

Y finalmente, para revertir sólo los cambios hasta el momento:

-- Roll back changes...
IF @startingTranCount > 0
    ROLLBACK TRANSACTION MySavePointName
ELSE
    ROLLBACK TRANSACTION

Otros consejos

Extensión La respuesta de Brian B..

Esto garantiza que el nombre del punto de guardado sea único y utilice las nuevas funciones TRY/CATCH/THROW de SQL Server 2012.

DECLARE @mark CHAR(32) = replace(newid(), '-', '');
DECLARE @trans INT = @@TRANCOUNT;

IF @trans = 0
    BEGIN TRANSACTION @mark;
ELSE
    SAVE TRANSACTION @mark;

BEGIN TRY
    -- do work here

    IF @trans = 0
        COMMIT TRANSACTION @mark;
END TRY
BEGIN CATCH
    IF xact_state() = 1 OR (@trans = 0 AND xact_state() <> 0) ROLLBACK TRANSACTION @mark;
    THROW;
END CATCH

He utilizado este tipo de administrador de transacciones en mis procedimientos almacenados:

    CREATE PROCEDURE Ardi_Sample_Test  
        @InputCandidateID INT  
    AS  
        DECLARE @TranCounter INT;  
        SET @TranCounter = @@TRANCOUNT;  
        IF @TranCounter > 0  
            SAVE TRANSACTION ProcedureSave;  
        ELSE  
            BEGIN TRANSACTION;  
        BEGIN TRY  

            /*
            <Your Code>
            */

            IF @TranCounter = 0  
                COMMIT TRANSACTION;  
        END TRY  
        BEGIN CATCH  
            IF @TranCounter = 0  
                ROLLBACK TRANSACTION;  
            ELSE  
                IF XACT_STATE() <> -1  
                    ROLLBACK TRANSACTION ProcedureSave;  

            DECLARE @ErrorMessage NVARCHAR(4000);  
            DECLARE @ErrorSeverity INT;  
            DECLARE @ErrorState INT;  
            SELECT @ErrorMessage = ERROR_MESSAGE();  
            SELECT @ErrorSeverity = ERROR_SEVERITY();  
            SELECT @ErrorState = ERROR_STATE();  

            RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState);  
        END CATCH  
    GO  
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top