文档章节

谈谈基于SQL Server 的Exception Handling[上篇]

jswjia
 jswjia
发布于 2015/01/20 16:53
字数 1867
阅读 6
收藏 0

对于所有的开发人员来说,Exception Handling是我们每天都要面对的事情。对于基于Source CodeException Handling,我想大家已经司空见惯了,但是对于Database级别的Exception Handling,就没有那么常见了。在这篇文章中,我将会介绍我对于基于Database编程中Exception Handling的一些粗浅的认识:在编写Stored Procedure时,如何抛出一个可预知的ExceptionADO.NET如何处理从Database抛出的Exception,如何保存基于Database ExceptionError 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本质上就是返回上一个语句执行遇到的ErrorID号,所以这并不是一个随机的值,无论是10/0还是100/0@@ERROR返回的结果都是一样的。

由于可以通过@@ERROR得到Error的类型,可以帮助我们在编写Stored Procedure的过程中,借助这个@@ERROR进行一些流程的控制。比如我们知道Error Number547代表违反外键约束,我们就可以通过@@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 messageError 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语句就将@@ERROR8134变成了0。不过想想也很简单,IF语句本身也是一个执行语句,在执行过程中并没有遇到Error,所以@@ERROR应该返回0。这也是我在上面的Sample中通过SET @error = @@ERROR@@ERROR进行预存的原因。

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

我发现很多程序员喜欢使用Output参数来处理这些预知的异常。比如:我们需要编写一个添加UserStored procedureuser 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
代表被你抛出的ErrorMessage,你可以同国3中方式来表示Messagemsg_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,…)一样,我们可以在一个一个参数中使用{0G}{1D}这样的站位符和进行格式处理的字符,这些站位符由后面的参数来填充。在这里也一样,在message中你一可以添加站位符,这着站位符由Argument来填充,具体如何定义,可以参阅SQL Server 2005 Books Online.

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

明白了RAISERROR如何使用了后,我们可以修改我们的先前创建UserStored 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))   



[原创]谈谈基于SQL Server的Exception Handling - PART I
[原创]谈谈基于SQL Server 的Exception Handling - PART II
[原创]谈谈基于SQL Server 的Exception Handling - PART III 
  • © 著作权归作者所有

    jswjia
    粉丝 1
    博文 15
    码字总数 29863
    作品 0
    郑州
    程序员
    私信 提问
    如何解决EnterLib异常处理框架最大的局限——基于异常"类型"的异常处理策略

    个人觉得EnterLib的EHAB(Exception Handling Application Block)是一个不错的异常处理框架,借助于EHAB,我们可以配置的方式来自定义异常处理策略,从而带来最大的灵活性和可维护性。但是,...

    长平狐
    2012/09/04
    176
    0
    SQL 2017 SQLPS执行Ad-SqlAvailabilityDatabase异常

    SQL Server 2017 SQLPS执行Ad-SqlAvailabilityDatabase遇到问题 这个错误简直逆天,查了下该cmdlet的帮助 常规参数里有Debug。 执行Debug,输出如下: PS C:Windowssystem32> Add-SqlAvailab...

    UltraSQL
    2018/05/23
    0
    0
    Comparison of different SQL implementations(整理)

    Comparison of different SQL implementations The goal of this page — which is a work in progress — is to gather information relevant for people who are porting SQL from one pr......

    晨曦之光
    2012/04/12
    334
    0
    Net Core平台灵活简单的日志记录框架NLog+SqlServer初体验

    Net Core平台灵活简单的日志记录框架NLog+SqlServer初体验 前几天分享的"[Net Core平台灵活简单的日志记录框架NLog+Mysql组合初体验][http://www.cnblogs.com/yilezhu/p/9416439.html]" 反响...

    依乐祝
    2018/08/09
    0
    0
    求帮助 jdbc连接数据库出错!谢谢

    希望有好心人帮忙解答卡在这里了学习无法进行了。。谢谢帮忙 一直无法建立连接:有如下3个错误{ java.sql.SQLException: No suitable driver found for jdbc:microsoft:sqlserver://localhos...

    刘轩麟
    2014/03/19
    210
    2

    没有更多内容

    加载失败,请刷新页面

    加载更多

    调用约定

    对于常见的指令集,在指令层面没有所谓的“函数”概念,只有“子程序”概念。子程序是存储在“主程序”之外的一段指令。子程序通过call指令调用,通过ret指令返回。子程序可以使用内存、堆栈...

    tommwq
    46分钟前
    3
    0
    设计类题目

    1. 订单 和 退货单之间有什么关系? 答:退货单是 用 用户提交退货 和 订单生成的 或者 订单和退货单都是一张单子,用一个状态标识 2. 在这种由源头单生成的流程中,第二张单子是怎样生成的?...

    杨凯123
    今天
    5
    0
    读写锁分离

    java.util.concurrent.locks包定义了两个锁类, 我们已经讨论的ReentrantLock类和 ReentrantReadWriteLock 类。 如果很多线程从一个数据结构读取数据而很少线程修改其中数 据的话, 后者是十...

    ytuan996
    今天
    6
    0
    金钱焦虑症测试 -- 人人都有吧?

    你经常觉得钱不够花,被金钱困扰着吗?试试这个焦虑量表测试,测试一下你的金钱焦虑指数吧。请选择选一个最适合自己态度的答案。买买买的欲望高吗?又是一个节日,有打折活动;又被种草一个化...

    蛤蟆丸子
    今天
    4
    0
    JAVA-LOCK之底层实现原理(源码分析)

    首先和Synchronized(可以参考) 的不同之处,Lock完全用Java写成,在java这个层面是无关JVM实现的。其实现都依赖java.util.concurrent.AbstractQueuedSynchronizer类,简称AQS。 简单说来,...

    小海bug
    今天
    7
    0

    没有更多内容

    加载失败,请刷新页面

    加载更多

    返回顶部
    顶部