【问题标题】:How to rollback transaction in a stored procedure?如何在存储过程中回滚事务?
【发布时间】:2019-11-27 20:05:38
【问题描述】:

当内部 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
*/

【问题讨论】:

  • 在哪一行出现此错误?
  • dbo.sp_executesql @statement = N'CREATE PROCEDURE [dbo].[pcaller] AS' 声明的其余部分在哪里..?
  • 您的BEGIN TRAN 没有COMMIT;你只有一个ROLLBACK如果 @@TRANCOUNT 的值大于0
  • @Lamu:是的,在调用者中 BEGIN TRAN,然后在内部 proc 这个事务回滚。 FInally trancount=0,但是当我调用 exec pcaller 时它会产生错误。

标签: sql-server tsql


【解决方案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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-03-22
    • 2020-07-08
    • 2020-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多