9.6 SQL Server异常处理
SQL Server异常处理
TRY CATCH
简介
TRY CATCH
可以很好的处理SQL Server中的异常。要使用TRY CATCH
,首先要在BEGIN TRY…END TRY
块中放置一组可能导致异常的T-SQL语句,如下所示:
BEGIN TRY -- 可能导致异常的语句 END TRY
然后在TRY
块之后立即使用BEGIN CATCH…END CATCH
块:
BEGIN CATCH -- 处理异常的语句 END CATCH
以下是完整的TRY CATCH结构:
BEGIN TRY -- 可能导致异常的语句 END TRY BEGIN CATCH -- 处理异常的语句 END CATCH
如果TRY
块里边的语句完成且没有错误,则CATCH
块不会被执行。但是,如果TRY
块中有语句导致异常,则将执行CATCH
块中的语句。
CATCH块中的函数
在CATCH
块中,可以使用以下函数获取发生错误的详细信息:
ERROR_LINE()
返回发生异常的行号。ERROR_MESSAGE()
返回生成的错误消息的完整内容。ERROR_PROCEDURE()
返回发生错误的存储过程或触发器的名称。ERROR_NUMBER()
返回发生的错误的编号。ERROR_SEVERITY()
返回所发生错误的严重级别。ERROR_STATE()
返回发生错误的状态号。
注意,这些函数只能在CATCH
块中使用,如果在其他地方使用,所有这些函数将返回NULL。
嵌套的TRY CATCH
可以将TRY CATCH构造嵌套在另一个TRY CATCH构造中。但是,TRY块或CATCH块都可以包含嵌套的TRY CATCH,例如:
BEGIN TRY --- 可能导致异常的语句 END TRY BEGIN CATCH -- 处理异常的语句 BEGIN TRY --- 嵌套的TRY块 END TRY BEGIN CATCH --- 嵌套的CATCH块 END CATCH END CATCH
简单示例
首先,创建一个名为usp_divide
的存储过程,将两个数字相除:
CREATE PROC usp_divide( @a decimal, @b decimal, @c decimal output ) AS BEGIN BEGIN TRY SET @c = @a / @b; 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; GO
在这个存储过程中,我们将公式放在TRY
块中,并在CATCH
块中调用CATCH
块函数ERROR_*
。
然后,调用usp_divide
存储过程将10除以2:
DECLARE @r decimal; EXEC usp_divide 10, 2, @r output; PRINT @r;
输出:
5
因为TRY块中没有发生异常,所以存储过程在TRY块处完成。
尝试通过调用usp_divide
存储过程将20除以0试试:
DECLARE @r2 decimal; EXEC usp_divide 10, 0, @r2 output; PRINT @r2;
下图显示了输出:
由于公式导致的除零错误,所以报错后不再执行TRY
块后续代码,直接执行CATCH
块内的语句,该语句返回错误的详细信息。
在事务中使用TRY CATCH的示例
新建两张表,sale.persons
(人员)和sales.deals
(交易)用于示例:
CREATE TABLE sales.persons ( person_id INT PRIMARY KEY IDENTITY, first_name NVARCHAR(100) NOT NULL,-- 名字 last_name NVARCHAR(100) NOT NULL-- 性别 ); CREATE TABLE sales.deals ( deal_id INT PRIMARY KEY IDENTITY, person_id INT NOT NULL, -- 交易人 deal_note NVARCHAR(100), -- 备注 FOREIGN KEY(person_id) REFERENCES sales.persons(person_id) ); insert into sales.persons(first_name, last_name) values ('John','Doe'), ('Jane','Doe'); insert into sales.deals(person_id, deal_note) values (1,'Deal for John Doe');
然后,创建一个名为usp_report_error
的新存储过程,该存储过程用于在CATCH
块中用于报告错误的详细信息:
CREATE PROC usp_report_error AS SELECT ERROR_NUMBER() AS ErrorNumber ,ERROR_SEVERITY() AS ErrorSeverity ,ERROR_STATE() AS ErrorState ,ERROR_LINE () AS ErrorLine ,ERROR_PROCEDURE() AS ErrorProcedure ,ERROR_MESSAGE() AS ErrorMessage; GO
然后,开发一个新的存储过程,从sales.persons
表中删除一行:
CREATE PROC usp_delete_person( @person_id INT ) AS BEGIN BEGIN TRY BEGIN TRANSACTION; -- 删除一个人员 DELETE FROM sales.persons WHERE person_id = @person_id; -- 如果删除成功,提交事务 COMMIT TRANSACTION; END TRY BEGIN CATCH -- 如果出了异常则报告错误,展示错误消息 EXEC usp_report_error; -- 测试事务是否不可提交。 IF (XACT_STATE()) = -1 BEGIN PRINT N'The transaction is in an uncommittable state.' + 'Rolling back transaction.' ROLLBACK TRANSACTION; END; -- 测试事务是否可提交。 IF (XACT_STATE()) = 1 BEGIN PRINT N'The transaction is committable.' + 'Committing transaction.' COMMIT TRANSACTION; END; END CATCH END; GO
在这个存储过程中,使用XACT_STATE()
函数在CATCH
块中执行COMMIT TRANSACTION
或ROLLBACK TRANSACITION
之前检查事务的状态。
然后,调用usp_delete_person
存储过程删除id为2的人员:
没有发生异常。
最后,调用存储过程usp_delete_person
删除id为1的人员:
EXEC usp_delete_person 1;
发生以下错误(因为人员1被交易表中的记录引用着,所以报错):
THROW
简介
THROW
语句用于抛出异常,并将执行传输到TRY CATCH
的CATCH
块。
语法:
THROW [error_number,message ,state ];
其中:
error_number
error_number
是一个代表异常的整数。error_number
必须大于50000
且小于或等于2147483647
。
message
message
是一个描述异常的NVARCHAR(2048)
类型的字符串。
state
state
是一个TINYINT
,其值介于0和255之间。状态表示与消息关联的状态。
如果没有为THROW语句指定任何参数,则必须将THROW声明放在CATCH块中:
BEGIN TRY -- 可能导致异常的语句 END TRY BEGIN CATCH -- 处理异常的语句 THROW; END CATCH
在这种情况下,THROW
语句将CATCH
块捕获的错误再抛出。
请注意,THROW
语句之前的语句必须以分号(;
)结束
示例
A) 以下示例使用THROW语句引发异常:
THROW 50005, N'An error occurred', 1;
输出:
Msg 50005, Level 16, State 1, Line 1 An error occurred
B)使用THROW语句重新引发异常
首先,为演示创建一个新表t1:
CREATE TABLE t1( id int primary key ); GO
然后,使用CATCH
块中不带参数的THROW
语句重新抛出捕获的错误:
BEGIN TRY INSERT INTO t1(id) VALUES(1); -- cause error INSERT INTO t1(id) VALUES(1); END TRY BEGIN CATCH PRINT('Raise the caught error again'); THROW; END CATCH
输出:
(1 row affected) (0 rows affected) Raise the caught error again Msg 2627, Level 14, State 1, Line 10 Violation of PRIMARY KEY constraint 'PK__t1__3213E83F906A55AA'. Cannot insert duplicate key in object 'dbo.t1'. The duplicate key value is (1).
在这个示例中,第一条INSERT
语句成功了。但是,由于主键约束,第二个失败。因此,THROW
语句再次引发CATCH
块捕获的错误。
C)使用FORMATMESSAGE函数
与RAISERROR
语句不同,THROW
语句不允许您替换消息文本中的参数。因此,要模拟此功能,可以使用FORMATMESSAGE()
函数。
以下语句将自定义消息添加到sys.messages
目录视图中:
EXEC sys.sp_addmessage @msgnum = 50010, @severity = 16, @msgtext = N'The order number %s cannot be deleted because it does not exist.', @lang = 'us_english'; GO
此语句使用message_id
50010
并将%s
占位符替换为订单id
'1001':
DECLARE @MessageText NVARCHAR(2048); SET @MessageText = FORMATMESSAGE(50010, N'1001'); THROW 50010, @MessageText, 1;
输出:
Msg 50010, Level 16, State 1, Line 8 The order number 1001 cannot be deleted because it does not exist.
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· DeepSeek “源神”启动!「GitHub 热点速览」
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器