如何在存储过程中回滚事务?

时间:2019-11-27 10:52:59

标签: sql-server tsql

当内部SP尝试回滚事务时,它完成并出现错误:

  

消息266,级别16,状态2,过程ptest,第0行[批处理开始行   37] EXECUTE之后的交易计数表明   BEGIN和COMMIT语句。上一个计数= 1,当前计数= 0。

是否可以在内部SP内部回滚事务?

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ptest]') AND type in (N'P', N'PC'))
BEGIN
EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[ptest] AS'
END
GRANT EXECUTE on [dbo].[ptest] to public;
GO
ALTER  PROCEDURE [dbo].[ptest]
@parrollback bit = 0
AS
BEGIN
SET NOCOUNT ON
SET XACT_ABORT OFF
select @@TRANCOUNT as '@@TRANCOUNT:[ptest] '
if @parrollback is not null and @parrollback>0
    if @@TRANCOUNT>0 rollback tran;
END
GO

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[pcaller]') AND type in (N'P', N'PC'))
BEGIN
EXEC dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[pcaller] AS'
END
GRANT EXECUTE on [dbo].[pcaller] to public;
GO
ALTER  PROCEDURE [dbo].[pcaller]
AS
BEGIN
SET NOCOUNT ON
begin tran
select @@TRANCOUNT as '@@TRANCOUNT: before [ptest]'
exec ptest 1
select @@TRANCOUNT as '@@TRANCOUNT: after [ptest] '
if @@TRANCOUNT>0 rollback tran;
END
GO
-------------

exec pcaller 

/*
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ptest]') AND type in (N'P', N'PC'))
    drop proc pcaller
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ptest]') AND type in (N'P', N'PC'))
    drop proc ptest
*/

1 个答案:

答案 0 :(得分:1)

尝试不处理子过程中的父事务(XACT_STATE()= -1时例外)。在启动该交易的“执行”级别处理该交易。

如果某个过程在父事务中执行,则创建一个保存点并在需要时回滚到该保存点。捕获子过程的执行结果并在父级处理事务(如果父级是开始事务的事务)。

CREATE OR ALTER PROCEDURE [dbo].[ptest] @parrollback bit = 0
AS
BEGIN
    SET NOCOUNT ON
    SET XACT_ABORT OFF

    DECLARE @trancount INT = @@TRANCOUNT;

    IF @trancount = 0
    BEGIN
        BEGIN TRANSACTION;
    END
    ELSE
    BEGIN
        SAVE TRANSACTION MySavepoint;
    END

    --do stuff.........


    --when it is time to commit or check for errors
    --assume @parrollback is the main control criterium

    IF @parrollback = 1
    BEGIN
        IF @trancount = 0
        BEGIN
            ROLLBACK TRANSACTION;
            RETURN(0);
        END
        ELSE
        BEGIN
            ROLLBACK TRANSACTION MySavePoint
            RETURN (1); 
        END
    END

    --just handle @parrollback <> 1, for completeness of the test
    IF @trancount = 0
    BEGIN
        COMMIT TRANSACTION;
    END

    RETURN (0);
END
GO

CREATE OR ALTER  PROCEDURE dbo.pcaller
AS
BEGIN
    SET NOCOUNT ON
    DECLARE @ptestexec INT;
    BEGIN TRANSACTION

    select @@TRANCOUNT as '@@TRANCOUNT: before [ptest]'
    EXEC @ptestexec = dbo.ptest @parrollback = 1;

    IF @ptestexec = 1
    BEGIN
        ROLLBACK TRANSACTION
    END
    ELSE
    BEGIN
        COMMIT TRANSACTION
    END


    --execute ptest, outside of a transaction
    EXEC @ptestexec = dbo.ptest @parrollback = 0;
    SELECT @@TRANCOUNT AS trancount1;

    EXEC @ptestexec = dbo.ptest @parrollback = 1;
    SELECT @@TRANCOUNT AS trancount2;

    --execute ptest, outside of a transaction
    BEGIN TRANSACTION;

    --ptest executed in a parent transaction
    EXEC @ptestexec = dbo.ptest @parrollback = 0;
    SELECT @@TRANCOUNT AS trancount3; --ptest does not affect the parent transactions

    COMMIT TRANSACTION --or rollback
END
GO

EXEC dbo.pcaller
GO