2

我正在创建存储过程。此存储过程运行本地以及外部存储过程。为简单起见,我将调用本地服务器[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

4 回答 4

4

IMO 最简单的方法是

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

我也无法理解本地 proc 中多个 BEGIN TRANS / COMMIT 背后的原因。我的意思是,如果这是月末汇总,这不应该是一笔大交易而不是一堆小交易吗?否则你的 trans 1 和 2 可能会成功提交,但 3 会失败,就是这样。

名称由以下组成:

 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。本地过程,检查返回值并决定是否继续。

sp1 sp2 sp3 和 sp4 都在一个事务中运行,因为每个事务都有多个事务对我来说并没有多大意义。

于 2012-11-30T11:02:07.407 回答
0

您可以尝试将两个存储过程都执行到单独TRY CATCH的块中并检查ERROR_NUMBERCATCH 块中的对应关系。如果 ERROR_NUMBER 与您得到的错误相同,您可以简单地returnraiseerror根据您的要求。

是否会导致致命错误。请检查异常中的错误严重程度

于 2012-11-28T09:37:29.263 回答
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

我之前没有使用过这个设置,所以我不确定它是否能满足你的需要。

于 2012-11-30T00:03:01.333 回答
0

如果您不能或不想使用 DTC,并且不想使用 CLR,那么您需要最后调用远程 sp,因为您将无法回滚远程 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.
    }
} 
于 2012-12-04T17:22:12.927 回答