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.