如果远程存储过程失败,则返回

时间:2012-11-27 21:40:44

标签: sql-server sql-server-2008 stored-procedures remote-server

我正在创建存储过程。此存储过程运行本地以及外部存储过程。为简单起见,我将调用本地服务器[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提供程序...返回消息“合作伙伴事务管理器已禁用其对远程/网络事务的支持。”。

我知道我无法在本地为远程过程运行事务。

如果程序的任何部分失败,我如何确保此程序将退出并回滚?

备注

  • 关于组合简单程序,其中一些是单独使用的。

4 个答案:

答案 0 :(得分:4)

IMO最简单的方法是

  • 将返回值添加到远程过程。
  • 将远程proc包装到事务中并尝试catch(在远程proc中)。如果发生错误则返回false。
  • 如果为false,则在本地存储过程中,只是不要继续。

我也无法理解本地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与您收到的错误相同,则可以根据您的要求returnraiseerror

是否会导致致命错误。请检查异常中的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.
    }
}