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 TRANSACTIONROLLBACK TRANSACITION之前检查事务的状态。

然后,调用usp_delete_person存储过程删除id为2的人员:

没有发生异常。

最后,调用存储过程usp_delete_person删除id为1的人员:

  EXEC usp_delete_person 1;

发生以下错误(因为人员1被交易表中的记录引用着,所以报错):

THROW

简介

THROW语句用于抛出异常,并将执行传输到TRY CATCHCATCH块。
语法:

  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.
 
分类: SQL Server

posted on 2025-04-21 16:53  漫思  阅读(108)  评论(0)    收藏  举报

导航