[聚合文章] 谈谈基于SQL Server 的Exception Handling[上篇]

SQL Server 2017-10-31 16 阅读

对于所有的开发人员来说, Exception Handling 是我们每天都要面对的事情。对于基于 Source Code Exception Handling ,我想大家已经司空见惯了,但是对于 Database 级别的 Exception Handling ,就没有那么常见了。在这篇文章中,我将会介绍我对于基于 Database 编程中 Exception Handling 的一些粗浅的认识:在编写 Stored Procedure 时,如何抛出一个可预知的 Exception ADO.NET 如何处理从 Database 抛出的 Exception ,如何保存基于 Database Exception Error Message ,如何在 Database .NET Application 之间进行消息的传递 [ 注:这里的 Database 主要指 SQL Server]

@@ERROR
RAISEERROR
TRY CATCH & Return Error message & sys.messages ADO.NET Exception Handling SqlException InfoMessage

一、     @@ERROR

@@ERROR 是我们经常使用的系统函数,其返回类型为 INT ,用以表示上一个语句的执行是否遇到错误, 0 便是语句正常执行,非 0 则以为着某个错误的产生。比如下面的一个例子:我在 SQL Server Management Studio 中执行下面一段简单的 SQL

DECLARE @result INT

SET @result = 10 / 0

PRINT @@ERROR

Go

会得到如下的执行结果:


由于除零导致错误的产生,使
@@ERROR 的值变成 8134 。实际上 8134 是一个 Error Number ,代表某个系统定义的 Error ,每个预定义的 Error 由一个唯一的 Error Number 来唯一标识。 @@ERROR 本质上就是返回上一个语句执行遇到的 Error ID 号,所以这并不是一个随机的值,无论是 10/0 还是 100/0 @@ERROR 返回的结果都是一样的。

由于可以通过 @@ERROR 得到 Error 的类型,可以帮助我们在编写 Stored Procedure 的过程中,借助这个 @@ERROR 进行一些流程的控制。比如我们知道 Error Number 547 代表违反外键约束,我们就可以通过 @@ERROR 的结果是否等于 547 来进行异常的处理:

UPDATE T_USERS_IN_ROLES  SET [ USER_ID ] = ' dummy text '

IF @@ERROR = 547

PRINT ' The user is not existent

'

通过我们上面一个截图我们发现,在

SQL Server Management Studio 中执行任何一个语句的时候,如果遇到一个预定义的 Error ,会打印出相关的 Error Message 。这些 Error Message 到底是如何存储的呢? Error message Error Numbder

又是如何进行关联的呢?

实际上, SQL Server 通过一个名为 sys.messages 的系统表来存储关于 Error 的一些信息 [ 关于 sys.messages ,在后续的章节中还会提及 ] 。下面是 sys.messages 的结构: message_id 不仅仅代表 message 的唯一标识,对于一个预定义的 Error ,其 Error number 就是这个 message_id 。由于 Localization 的需要,我们需要为不同的语言定义不同的 Message ,这些 Message 共享一个 message_id, 具体采用何种语言通过 luange_id 来标识。 Severity 代表 Error 的严重程度,我将在后续部分专门介绍。 is_event_logged 是一个 Indicator ,表明出现该 Error 是否需要在 Event log 中进行日志记录, text 当然就是 message 文本了。


我们可以通过下面的
SQL 来进行验证:

DECLARE @result INT

DECLARE @error INT

SET @result = 5 / 0

SET @error = @@ERROR

SELECT @error ,sys.messages. * FROM sys.messages  WHERE message_id  = @error

Go

下面是执行的结果:


对于
@@ERROR ,有一点需要特别提醒的是:它仅仅代表前一个语句执行的 Error Number ,之后任何一段语句的执行都会改变 @@ERROR 的值,甚至是一个 IF 语句。我想通过下面一段 SQL ,你肯定会后一个深刻的认识:


我们可以看到,一个简简单单的
IF 语句就将 @@ERROR 8134 变成了 0 。不过想想也很简单, IF 语句本身也是一个执行语句,在执行过程中并没有遇到 Error ,所以 @@ERROR 应该返回 0 。这也是我在上面的 Sample 中通过 SET @error = @@ERROR @@ERROR 进行预存的原因。

@@ERROR 实际上代表的是在编写 SQL 或者 Stored procedure 中对异常的识别,大多数我们通过 @@ERROR 来判断一段 SQL 语句是否成功执行,保证没有遇到不可预知的异常。对于一些可以预知的异常在 SQL 中又该如何处理呢?

我发现很多程序员喜欢使用 Output 参数来处理这些预知的异常。比如:我们需要编写一个添加 User Stored procedure user name 具有唯一性,添加一个和 database 中同名的 user 显然是不合法的,在很多情况下通过一个 Output 参数来返回操作最终执行的情况,比如:

CREATE Procedure P_USERS_I

(

@user_id varchar ( 50 ),

@user_name nvarchar ( 256 ),

@flag INT OUTPUT

)

AS

IF ( EXISTS ( SELECT * FROM dbo.T_USERS  WHERE LOWERED_USER_NAME  = LOWER ( @user_nameOR [ USER_ID ] = @user_id ))

BEGIN

SET @flag = - 1

RETURN

END

INSERT INTO dbo.T_USERS

( [ USER_ID ]

, [ USER_NAME ]

,LOWERED_USER_NAME)

VALUES ( @user_id@user_nameLOWER ( @user_name ))     

SET @flag =

@@ERROR

很显然通过 flag output 参数可以得到 User 的创建操作最终执行的结果: -1 代表重名, 0 代表成功,大于 0 代表出现不可预知的异常。

说实话,我不太喜欢这样的异常处理方式,其实这并不是说这样的处理不好,大部分还是由于个人喜好决定。我觉得,既然添加一个同名的 User 本身代表一种 Exception ,从语义上讲,认为地抛出这样 Exception 的方式好像更加合理一点,所以我们喜欢通过调用 RAISEERROR 的方式将一个 Error 抛出。

二、        RAISEERROR

RAISEEROR 是一个系统函数,用于奖某个可以预知的 Exception 抛出,供 Application 捕捉并处理,下面是 RAISERROR 的声明:

RAISERROR ( { msg_id  | msg_str  | @local_variable }

{ ,severity ,state }

[  ,argument [ , ] ] )

[  WITH option [ , ] ]

msg_id | msg_str | @local_variable 代表被你抛出的 Error Message ,你可以同国 3 中方式来表示 Message msg_id 带面 sys.messages 中的 message_id, msg_str 表示一个自定义的文本, @local_variable 则表示 message 的变量。

Severity 一个代表严重程度的数字,其范围为 0-25 ,其中 0-18 可以由任何用户指定, 19-25 只能由 sysadmin 指定。一般地, 0-10 为严重程度很低的错误, 11-18 来高级别的错误, 19-25 代表非常严重的错误,以致在执行完成之后会终止当前的 Session

State 一个 0-127 的整数,代表一个错误状态,对于在多个地方抛出 Message 一致的的情况,将 State 在不同的地方设置在不同的值,在 Debug 的时候可以很快知道是哪里出错了,所以 State 具有很现实的意义。

Argument 向我们调用 String.Format(string,…) 一样,我们可以在一个一个参数中使用 {0 G}{1 D} 这样的站位符和进行格式处理的字符,这些站位符由后面的参数来填充。在这里也一样,在 message 中你一可以添加站位符,这着站位符由 Argument 来填充,具体如何定义,可以参阅 SQL Server 2005 Books Online.

WITH option [ ,...n ]: 代表一些额外的选项 , LOG 表示进行日志记录, NOWAIT 表示立即将 Message 递交到客户端, SETERROR 强制将当前真实的 @@ERROR 或者 message_id 返回到客户端。

明白了 RAISERROR 如何使用了后,我们可以修改我们的先前创建 User Stored Procedure

CREATE Procedure P_USERS_I

(

@user_id varchar ( 50 ),

@user_name nvarchar ( 256 )

)

AS

IF ( EXISTS ( SELECT * FROM dbo.T_USERS  WHERE LOWERED_USER_NAME  = LOWER ( @user_nameOR [ USER_ID ] = @user_id ))

BEGIN

RAISERROR ( ' This role is already existent ' , 16 , 1 )

END

INSERT INTO dbo.T_USERS

( [ USER_ID ]

, [ USER_NAME ]

,LOWERED_USER_NAME)

VALUES ( @user_id@user_nameLOWER

(

@user_name

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。