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