【问题标题】:writing a transaction in t-sql and error handling在 t-sql 中编写事务和错误处理
【发布时间】:2010-01-24 15:37:25
【问题描述】:

你认为在 t-sql 中编写事务有更好的方法吗?是否有更好的方法可以提高使用此事务的应用程序的可维护性和性能?

-- Description: Insert email Receiver under specified subject
-- =============================================
ALTER PROCEDURE [Contact].[Receiver_stpInsert]
    @First_Name nvarchar(30),
    @Last_Name nvarchar(30),
    @Email varchar(60),
    @Subject_Id int
AS
BEGIN   
    SET NOCOUNT ON;

    DECLARE @error_num int;


    BEGIN TRANSACTION 

    INSERT INTO Contact.Receiver(First_Name, Last_Name, Email) VALUES(@First_Name, @Last_Name, @Email); 

    SET @error_num = @@ERROR;
    IF (@error_num <> 0)
        BEGIN
            ROLLBACK;
            RETURN;
        END

    DECLARE @rec_record_id int;
    SET @rec_record_id = (SELECT Record_Id FROM Contact.Receiver WHERE Email = @Email);

    SET @error_num = @@ERROR;
    IF (@error_num <> 0)
        BEGIN
            ROLLBACK;
            RETURN;
        END

    INSERT INTO Contact.Receiver_Subject(Receiver_Id, Subject_Id) VALUES(@rec_record_id, @Subject_Id);

    SET @error_num = @@ERROR;
    IF (@error_num <> 0)
        BEGIN
            ROLLBACK;
            RETURN;
        END

    SET @error_num = @@ERROR;
    IF (@error_num <> 0)
        BEGIN
            ROLLBACK;
            RETURN;
        END
    ELSE
        BEGIN   
            Commit;

        END

END

【问题讨论】:

    标签: sql-server tsql


    【解决方案1】:

    如果您使用的是 SQL 2005 或更高版本,则可以使用 TRY...CATCH 块,如下所示:

    BEGIN TRY
        BEGIN TRANSACTION;
    
        INSERT INTO Contact.Receiver(First_Name, Last_Name, Email) VALUES (@First_Name, @Last_Name, @Email); 
        ... other inserts etc 
        ...
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;
    END CATCH;
    

    这样,您就不会一直重复检查@@ERROR 的相同代码块。如果您想知道发生了什么错误,在 BEGIN CATCH 块中您可以获得各种信息:

    • ERROR_NUMBER() 返回错误的编号。
    • ERROR_SEVERITY() 返回严重性。
    • ERROR_STATE() 返回错误状态编号。
    • ERROR_PROCEDURE() 返回存储过程或触发器的名称 发生错误的位置。
    • ERROR_LINE() 返回导致 错误。
    • ERROR_MESSAGE() 返回错误消息的完整文本。文本 包括为任何提供的值 可替代的参数,例如 长度、对象名称或时间。

    【讨论】:

    • 我会将 COMMIT TRANSACTION 放入 BEGIN TRY....END TRY 块中 - 而不是在整个语句之后。那不是更容易更准确吗?
    • 为什么不把BEGIN TRANSACTION也放在BEGIN TRY之后呢?
    • 这是 6 年前的事了....不记得我当时在想什么 ;) 但是,是的,我会将事务放在 BEGIN TRY 中。我已经更新了答案。
    • 记得在你这样做的时候重新抛出错误!!我看到很多堆栈溢出 Try Catch 解决方案没有重新抛出,这不可避免地导致现实世界的错误探索。在实施之前,请查看下面@Remus_Rusanu 的答案。
    【解决方案2】:

    很长一段时间以来,我一直在提倡使用TRY/CATCH and nested transactions in stored procedures。

    与@@ERROR 检查相比,此模式不仅为您提供了大大简化的 TRY/CATCH 块的错误处理,而且还为过程调用提供了全有或全无的嵌套语义。

    如果在事务的上下文中调用该过程,则该过程仅回滚它自己的更改,并让调用者决定是回滚嵌入事务还是尝试替代错误路径。

    create procedure [usp_my_procedure_name]
    as
    begin
        set nocount on;
        declare @trancount int;
        set @trancount = @@trancount;
        begin try
            if @trancount = 0
                begin transaction
            else
                save transaction usp_my_procedure_name;
    
            -- Do the actual work here
    
    lbexit:
            if @trancount = 0   
                commit;
        end try
        begin catch
            declare @error int, @message varchar(4000), @xstate int;
            select @error = ERROR_NUMBER(), @message = ERROR_MESSAGE(), @xstate = XACT_STATE();
            if @xstate = -1
                rollback;
            if @xstate = 1 and @trancount = 0
                rollback
            if @xstate = 1 and @trancount > 0
                rollback transaction usp_my_procedure_name;
    
            raiserror ('usp_my_procedure_name: %d: %s', 16, 1, @error, @message) ;
            return;
        end catch   
    end
    

    这种方法的缺点是:

    • 它不适用于分布式事务。因为事务保存点与分布式事务不兼容,所以在需要分布式事务时不能使用此模式。恕我直言,分布式事务是邪恶的,无论如何都不应该使用。
    • 它改变了原来的错误。这个问题是 TRY/CATCH 块中固有的,您对此无能为力。准备处理原始 SQL Server 错误代码(如 1202、1205、2627 等)的应用程序必须更改以处理使用 TRY/CATCH 的 Transact-SQL 代码引发的上述 50000 范围内的错误代码.

    还有一点关于使用SET XACT_ABORT ON 的注意事项。此设置将导致批处理在任何错误时中止事务。这会引发任何 TRY/CATCH 事务处理基本上无用,我建议避免使用。

    【讨论】:

    • 第三个缺点是过度复制和粘贴...真希望有办法避免所有重复。
    • @Aaronaught:不幸的是,这是关于 Transact-SQL 的最新技术。只是对代码重用和简洁不友好的语言。通过工具(例如 XSTL+XML)生成代码通过消除编写 T-SQL 的重复性和容易出错的性质,大大缓解了该问题。
    • @RemusRusanu lbexit: 是什么? Google 只返回您的链接(以及您的其他 SO 帖子),但我的 AV 抱怨该链接。
    • @Alex 我在博客中简化示例的实际过程中有goto lbexit :)。该标签不必要地潜入博客并被复制。
    【解决方案3】:

    如果您有 SQL Server 2000 或之前的版本,那么可以 - 检查 @@ERROR 值基本上是您所能做的。

    在 SQL Server 2005 中,Microsoft 引入了 TRY...CATCH 结构,这使它变得更加容易:

    BEGIN TRY
      ......
      -- your T-SQL code here
      ......
    END TRY
    BEGIN CATCH
       SELECT 
          ERROR_NUMBER() AS ErrorNumber,
          ERROR_SEVERITY() AS ErrorSeverity,
          ERROR_STATE() AS ErrorState,
          ERROR_PROCEDURE() AS ErrorProcedure,
          ERROR_LINE() AS ErrorLine,
          ERROR_MESSAGE() AS ErrorMessage
    
        -- do other steps, if you want
    END CATCH
    

    【讨论】:

      【解决方案4】:

      不久前问过。 My answer 带有 TRY/CATCH 模板

      【讨论】:

        【解决方案5】:

        如果您使用的是 sql 2005 或更高版本,则应考虑使用 TRY CATCH 方法

        【讨论】:

          【解决方案6】:

          您可以将它全部包装在一个 try catch 中,然后您只需在一个地方编写回滚代码。详情请见this。

          【讨论】:

            猜你喜欢
            • 2011-03-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-09-28
            • 1970-01-01
            • 1970-01-01
            • 2015-11-04
            相关资源
            最近更新 更多