我正在创建存储过程。此存储过程运行本地以及外部存储过程。为简单起见,我将调用本地服务器[LOCAL]
和远程服务器[REMOTE]
。
这是一个简单的拓扑结构:
程序
USE [LOCAL]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[monthlyRollUp]
AS
SET NOCOUNT, XACT_ABORT ON
BEGIN TRY
EXEC [REOMTE].[DB].[table].[sp]
--This transaction should only begin if the remote procedure does not fail
BEGIN TRAN
EXEC [LOCAL].[DB].[table].[sp1]
COMMIT
BEGIN TRAN
EXEC [LOCAL].[DB].[table].[sp2]
COMMIT
BEGIN TRAN
EXEC [LOCAL].[DB].[table].[sp3]
COMMIT
BEGIN TRAN
EXEC [LOCAL].[DB].[table].[sp4]
COMMIT
END TRY
BEGIN CATCH
-- Insert error into log table
INSERT INTO [dbo].[log_table] (stamp, errorNumber,
errorSeverity, errorState, errorProcedure, errorLine, errorMessage)
SELECT GETDATE(), ERROR_NUMBER(), ERROR_SEVERITY(), ERROR_STATE(), ERROR_PROCEDURE(),
ERROR_LINE(), ERROR_MESSAGE()
END CATCH
GO
在远程过程上使用事务时,会抛出此错误:
OLE DB提供程序...返回消息“合作伙伴事务管理器已禁用其对远程/网络事务的支持。”。
我知道我无法在本地为远程过程运行事务。
如果程序的任何部分失败,我如何确保此程序将退出并回滚?
备注
答案 0 :(得分:4)
IMO最简单的方法是
我也无法理解本地proc中多个BEGIN TRANS / COMMIT背后的原因。我的意思是,如果这是月末汇总,shuldn这是一个大交易,而不是一堆小?否则你的trans 1和2可能会成功提交,但3会失败,那就是那个。
名称由c:
组成 CREATE PROC [remote].db.REMOTE_PROC (
@return_value int output
)
AS
BEGIN
SET XACT_ABORT ON;
BEGIN TRY
BEGIN TRANS
... do stuff ...
set @return_value = 1;
COMMIT;
END TRY
BEGIN CATCH
set @return_value = 0;
END CATCH
END
和本地proc
CREATE PROC [local].db.[monthlyRollUp] AS
BEGIN
SET XACT_ABORT ON;
declare @ret int;
EXECUTE [remote].dbo.REMOTE_PROC @return_value = @ret OUTPUT;
IF @ret = 0
PRINT 'ERROR :('
RETURN
END IF
BEGIN TRANS
-- one big transaction here
EXEC [LOCAL].[DB].[table].[sp1];
EXEC [LOCAL].[DB].[table].[sp2];
EXEC [LOCAL].[DB].[table].[sp3];
EXEC [LOCAL].[DB].[table].[sp4];
COMMIT;
END;
afair [remote] .dbo.REMOTE_PROC运行自己的事务空间,如果成功则返回1。本地proc,检查返回值并决定是否继续。
sp1 sp2 sp3和sp4都在一个单独的事务中运行,因为每个事务都有多个事务对我来说并没有多大意义。
答案 1 :(得分:0)
您可以尝试将两个存储过程执行到单独的TRY CATCH
块中,并检查CATCH块中的相应ERROR_NUMBER
。如果ERROR_NUMBER与您收到的错误相同,则可以根据您的要求return
或raiseerror
。
是否会导致致命错误。请检查异常中的error severity。
答案 2 :(得分:0)
我可能有点不清楚你想要什么。如果您需要整个monthlyRollUp SP在远程或本地过程失败时回滚,那么您将需要一个分布式事务协调器。这将允许服务器传递有关事务的信息并协调提交。即,两个服务器必须指示已获得所有必需的锁,然后在两个服务器上协调提交,以便操作是自动的。以下是设置DTC的一个示例: http://social.msdn.microsoft.com/forums/en-US/adodotnetdataproviders/thread/7172223f-acbe-4472-8cdf-feec80fd2e64/
如果您不希望远程过程参与/影响交易,您可以尝试设置:
SET REMOTE_PROC_TRANSACTIONS OFF;
http://msdn.microsoft.com/en-us/library/ms178549%28SQL.90%29.aspx
我之前没有使用过那个设置,所以我不确定它是否能达到你的需要。
答案 3 :(得分:0)
如果您不能或不想使用DTC,并且不想使用CLR,那么您需要拨打最后一个遥控器,因为您不会能够回滚远程sp调用。
SET NOCOUNT, XACT_ABORT ON
SET REMOTE_PROC_TRANSACTIONS OFF;
BEGIN TRY
DECLARE @ret INT
BEGIN TRAN
--Perform these in a transaction, so they all rollback together
EXEC [LOCAL].[DB].[table].[sp1]
EXEC [LOCAL].[DB].[table].[sp2]
EXEC [LOCAL].[DB].[table].[sp3]
EXEC [LOCAL].[DB].[table].[sp4]
--We call remote sp last so that if it fails we rollback the above transactions
--We'll have to assume that remote sp takes care of itself on error.
EXEC [REMOTE].[DB].[table].[sp]
COMMIT
END TRY
BEGIN CATCH
--We rollback
ROLLBACK
-- Insert error into log table
INSERT INTO [dbo].[log_table] (stamp, errorNumber,
errorSeverity, errorState, errorProcedure, errorLine, errorMessage)
SELECT GETDATE(), ERROR_NUMBER(), ERROR_SEVERITY(), ERROR_STATE(),ERROR_PROCEDURE(),
ERROR_LINE(), ERROR_MESSAGE()
END CATCH
如果本地sp依赖于远程存储过程的结果,那么你可以使用CLR sp(将需要EXTERNAL_ACCESS权限)并明确地管理事务(基本上,滚动你自己的DTC,但没有两个 - 阶段提交。您有效地延迟了远程提交。)
//C# fragment to roll your own "DTC" This is not true two-phase commit, but
//may be sufficient to meet your needs. The edge case is that if you get an error
//while trying to commit the remote transaction, you cannot roll back the local tran.
using(SqlConnection cnRemote = new SqlConnection("<cnstring to remote>"))
{
try {
cnRemote.Open();
//Start remote transaction and call remote stored proc
SqlTransaction trnRemote = cnRemote.BeginTransaction("RemoteTran");
SqlCommand cmdRemote = cnRemote.CreateCommand();
cmdRemote.Connection = cnRemote;
cmdRemote.Transaction = trnRemote;
cmdRemote.CommandType = CommandType.StoredProcedure;
cmdRemote.CommandText = '[dbo].[sp1]';
cmdRemote.ExecuteNonQuery();
using(SqlConnection cnLocal = new SqlConnection("context connection=true"))
{
cnLocal.Open();
SqlTransaction trnLocal = cnLocal.BeginTransaction("LocalTran");
SqlCommand cmdLocal = cnLocal.CreateCommand();
cmdLocal.Connection = cnLocal;
cmdLocal.Transaction = trnLocal;
cmdLocal.CommandType = CommandType.StoredProcedure;
cmdLocal.CommandText = '[dbo].[sp1]';
cmdLocal.ExecuteNonQuery();
cmdLocal.CommandText = '[dbo].[sp2]';
cmdLocal.ExecuteNonQuery();
cmdLocal.CommandText = '[dbo].[sp3]';
cmdLocal.ExecuteNonQuery();
cmdLocal.CommandText = '[dbo].[sp4]';
cmdLocal.ExecuteNonQuery();
//Commit local transaction
trnLocal.Commit();
}
//Commit remote transction
trnRemote.Commit();
} // try
catch (Exception ex)
{
//Cleanup stuff goes here. rollback remote tran if needed, log error, etc.
}
}