SQL Try Catch在存储过程中 - 我做得对吗?

时间:2015-09-11 16:49:03

标签: sql-server stored-procedures error-handling try-catch

我有一个执行大量插入的存储过程,每个插入都会返回一个SCOPE_IDENTITY()主键值,然后将其用作后续插入的外键。

为了防止由于前一个插入失败而在没有FK的情况下执行插入的情况,我将插入包装在try-catch块中,如下所示:

DECLARE @error INT;

BEGIN TRY
    insert statement here...
    SET @newId = SCOPE_IDENTITY();
END TRY
BEGIN CATCH
    -- assign error value to 
    SET @error = @@ERROR;
END CATCH

然后下一个插入在继续之前检查错误var:

IF @error = 0
BEGIN
    BEGIN TRY
        next insert statement here using `@newId` from previous qry...
        SET @nextId = SCOPE_IDENTITY();
    END TRY
    BEGIN CATCH
        // assign error value to 
        SET @error = @@ERROR;
    END CATCH
END

等等。这是Try-catch的合适应用还是过度杀伤?

3 个答案:

答案 0 :(得分:1)

如果您打算处理catch返回的错误,那就不是一种杀手锏。如果没有外键,技术上不会出现插入,因此您不会使用try / catch阻止无效数据。因此,如果第一次失败,没有try / catch,第二次插入仍会失败。

为了进一步采用这种方法,您可以考虑使用提交和回滚在事务中进行插入。请参阅此TechNet article中有关不可提交事务的部分。

答案 1 :(得分:1)

来自Here

  

TRY ... CATCH构造不能跨越多个批次。一个尝试...捕捉   构造不能跨越多个Transact-SQL语句块。对于   例如,TRY ... CATCH构造不能跨越两个BEGIN ... END块   Transact-SQL语句并不能跨越IF ... ELSE构造。

这意味着如果Try...Catch在内部块中发生,则嵌套的Try..Catch块不会影响外部Error块代码。

在这里,您希望向我们提供一个查询到另一个的结果,因此,您最好使用单个Try..Catch阻止

<强>原因

  

如果TRY块中包含的代码中没有错误,   当TRY块中的最后一个语句完成运行时,控制   在关联的END CATCH之后立即传递给语句   声明。 如果TRY中包含的代码中存在错误   block,control传递给相关CATCH中的第一个语句   块即可。如果END CATCH语句是存储中的最后一个语句   过程或触发器,控制权被传递回语句   调用存储过程或触发触发器。

因此,在这种情况下,在第一次错误发生后没有额外的执行恐惧。

示例:

使用嵌套Try...Catch

BEGIN try
  declare @param1 as int
    BEGIN Try
        set @param1='gkjk'
        select @param1
     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;  
        END CATCH
    BEGIN TRY
        select 'hello','i m in',200
    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;  
    END CATCH
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;
  END CATCH
  

它会给你两个结果集。

使用单Try...Catch

BEGIN TRY
    declare @param1 as int
     set @param1='gkjk'
     select @param1
     select 'hello','i m in',200
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;  

 END CATCH
  

这会给你单一结果。

答案 2 :(得分:1)

这样可以正常工作,但是如果你的意图是在第一次失败后(即在包裹的插入物的捕获范围内)在程序中不再做任何工作,那么你可以:

  • 仅使用一个try / catch块(并且流将从第一个失败的insert语句跳到catch块,跳过依赖于前一个的所有以下插入)
  • 如果您因为某种原因更喜欢使用多个try / catches,您可以简单地从catch块中重新抛出异常(仅在SQL Server 2014及更高版本上有效/支持),或者只是在catch块中返回pre-Sql Server 2014(在你完成任何清理工作之后)。

在2014年及之后,在catch块中使用THROW将重新抛出异常并立即退出当前语句批处理(参见here for details,如果在存储过程中使用,例如,这基本上意味着它将退出当前程序。)

作为使用catch块(2014 +)中的THROW的一个简单示例:

if object_id('tempdb..#TestRethrow') is not null
    drop table #TestRethrow;

CREATE TABLE #TestRethrow(ID INT PRIMARY KEY);

BEGIN TRY
    INSERT #TestRethrow(ID) VALUES(1);
END TRY
BEGIN CATCH
    PRINT 'In catch block 1 - SHOULD NOT FIRE.';
    THROW;
END CATCH;

print 'between catch blocks';

BEGIN TRY
    -- Unique key violation
    INSERT #TestRethrow(ID) VALUES(1);
END TRY
BEGIN CATCH
    PRINT 'In catch block 2 - WILL FIRE';
    THROW;
    PRINT 'At end of catch block 2 - SHOULD NOT FIRE';
END CATCH;

print 'out of catch block 2 - SHOULD NOT FIRE'
select 1;

go

print 'SHOULD FIRE - Another batch (assuming you are using SSMS for example, or another client that separates batches using GO';
go

如果使用SQL 2012或更早版本,您可以使用返回代替throw,但您可能希望首先对异常执行某些操作(即记录它,或使用较旧的RAISERROR语法引发它):< / p>

    if object_id('tempdb..#TestRethrow') is not null
    drop table #TestRethrow;

CREATE TABLE #TestRethrow(ID INT PRIMARY KEY);

BEGIN TRY
    INSERT #TestRethrow(ID) VALUES(1);
END TRY
BEGIN CATCH
    PRINT 'In catch block 1 - SHOULD NOT FIRE.';
    return;
END CATCH;

print 'between catch blocks';

BEGIN TRY
    -- Unique key violation
    INSERT #TestRethrow(ID) VALUES(1);
END TRY
BEGIN CATCH
    PRINT 'In catch block 2 - WILL FIRE';
    return
    PRINT 'At end of catch block 2 - SHOULD NOT FIRE';
END CATCH;

print 'out of catch block 2 - SHOULD NOT FIRE'
select 1;

go

print 'SHOULD FIRE - Another batch (assuming you are using SSMS for example, or another client that separates batches using GO';
go