SQL 在存储过程中尝试 Catch - 我做对了吗?
SQL Try Catch in stored procedure - am I doing it right?
我有一个执行多个插入的存储过程,每个插入都会 return 一个 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
然后下一个插入在继续之前检查错误变量:
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
的合适应用还是矫枉过正?
如果您打算处理 catch 返回的错误,这并不过分。没有外键就无法在技术上进行插入,因此您无法使用 try/catch 来防止无效数据。因此,如果没有 try/catch,如果第一次插入失败,您的第二次插入仍然会失败。
要进一步采用这种方法,您可以考虑在事务中进行插入,包括提交和回滚。请参阅此 TechNet article 中关于不可提交事务的部分。
来自Here
A TRY…CATCH construct cannot span multiple batches. A TRY…CATCH
construct cannot span multiple blocks of Transact-SQL statements. For
example, a TRY…CATCH construct cannot span two BEGIN…END blocks of
Transact-SQL statements and cannot span an IF…ELSE construct.
这意味着嵌套的 Try...Catch
块不会影响外部 Try..Catch
块代码,如果 Error
出现在内部块中。
这里你想要我们一个查询到另一个的结果,所以,你最好使用单个 Try..Catch
块
原因
If there are no errors in the code that is enclosed in a TRY block,
when the last statement in the TRY block has finished running, control
passes to the statement immediately after the associated END CATCH
statement. If there is an error in the code that is enclosed in a TRY
block, control passes to the first statement in the associated CATCH
block. If the END CATCH statement is the last statement in a stored
procedure or trigger, control is passed back to the statement that
called the stored procedure or fired the trigger.
所以,在这种情况下,第一次错误发生后没有额外的执行恐惧。
示例:
使用嵌套 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
It'll Give You Two Result Set.
使用单个 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
Where as This Will Give you Single Result.
这会很好地工作,但是如果您的目的是在第一次失败后(即在包装插入的捕获范围内)简单地不在过程中做任何更多的工作,那么您可以:
- 仅使用一个 try/catch 块(流程将从第一个失败的插入语句跳到 catch 块,跳过所有依赖于前一个的后续插入)
- 如果您出于任何原因更喜欢使用多个 try/catches,您可以简单地从 catch 块中重新抛出异常(仅 valid/supported on SQL Server 2014 和稍后)或者如果运行 pre-Sql Server 2014(在你完成任何你想做的清理之后)。
在 2014 年及以后,在 catch 块中使用 THROW 将重新抛出异常并立即退出当前语句批处理(参见 here for details,例如,如果在存储过程中使用,这基本上意味着它将退出当前程序。
作为在 catch 块中使用 THROW 的简单示例 (2014+):
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 或更早版本,您可以使用 return 代替抛出,但您可能希望先处理异常(即记录它,或者使用旧的 RAISERROR 语法引发它):
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
我有一个执行多个插入的存储过程,每个插入都会 return 一个 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
然后下一个插入在继续之前检查错误变量:
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
的合适应用还是矫枉过正?
如果您打算处理 catch 返回的错误,这并不过分。没有外键就无法在技术上进行插入,因此您无法使用 try/catch 来防止无效数据。因此,如果没有 try/catch,如果第一次插入失败,您的第二次插入仍然会失败。
要进一步采用这种方法,您可以考虑在事务中进行插入,包括提交和回滚。请参阅此 TechNet article 中关于不可提交事务的部分。
来自Here
A TRY…CATCH construct cannot span multiple batches. A TRY…CATCH construct cannot span multiple blocks of Transact-SQL statements. For example, a TRY…CATCH construct cannot span two BEGIN…END blocks of Transact-SQL statements and cannot span an IF…ELSE construct.
这意味着嵌套的 Try...Catch
块不会影响外部 Try..Catch
块代码,如果 Error
出现在内部块中。
这里你想要我们一个查询到另一个的结果,所以,你最好使用单个 Try..Catch
块
原因
If there are no errors in the code that is enclosed in a TRY block, when the last statement in the TRY block has finished running, control passes to the statement immediately after the associated END CATCH statement. If there is an error in the code that is enclosed in a TRY block, control passes to the first statement in the associated CATCH block. If the END CATCH statement is the last statement in a stored procedure or trigger, control is passed back to the statement that called the stored procedure or fired the trigger.
所以,在这种情况下,第一次错误发生后没有额外的执行恐惧。
示例:
使用嵌套 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
It'll Give You Two Result Set.
使用单个 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
Where as This Will Give you Single Result.
这会很好地工作,但是如果您的目的是在第一次失败后(即在包装插入的捕获范围内)简单地不在过程中做任何更多的工作,那么您可以:
- 仅使用一个 try/catch 块(流程将从第一个失败的插入语句跳到 catch 块,跳过所有依赖于前一个的后续插入)
- 如果您出于任何原因更喜欢使用多个 try/catches,您可以简单地从 catch 块中重新抛出异常(仅 valid/supported on SQL Server 2014 和稍后)或者如果运行 pre-Sql Server 2014(在你完成任何你想做的清理之后)。
在 2014 年及以后,在 catch 块中使用 THROW 将重新抛出异常并立即退出当前语句批处理(参见 here for details,例如,如果在存储过程中使用,这基本上意味着它将退出当前程序。
作为在 catch 块中使用 THROW 的简单示例 (2014+):
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 或更早版本,您可以使用 return 代替抛出,但您可能希望先处理异常(即记录它,或者使用旧的 RAISERROR 语法引发它):
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