以前的编程习惯一直不太好,不管是写C,C++,C#还是数据库脚本一般都是调试测试通过即可,从来不加try/catch等异常处理。现在开始为了良好的编程习惯和一个case,开始学着在有必要处理异常的时候增加异常处理代码。
SQL Server似乎以前错误处理功能不怎么样,现在肯定有了很大改进,但我肯定是没这个资格指手画脚。。。不管好坏,学会用是第一步。
1. 基础
1.1 Error Message
一般SQL Server的错误消息如下:
Server: Msg 547, Level 16, State 1, Procedure error_demo_sp, Line 2
UPDATE statement conflicted with COLUMN FOREIGN KEY constraint 'fk7_acc_cur'.
The conflict occurred in database 'bos_sommar', table 'currencies', column 'curcode'.
The statement has been terminated.
包括以下几个部分:
Message number:大于50000的为自定义消息,具体信息在master..sysmessages表里,可以用sp_addmessage来添加消息。
Severity level:0-25,0-10是信息或警告,编程错误为11-16,17-25为资源错误、硬件错误或SQL Server内部错误。>=20的错误会导致连接终止。
State:0-127,但是是undocumented。。。特例是对于OSQL和ISQL,如果State为127,会停止并将ERRORLEVEL设置为message number,用来处理安装脚本时的错误
Line:如果为0说明是procedure调用时出错。
客户端决定呈现的格式。
数据库引擎的错误处理分两个层面:T-SQL层面和应用程序代码层面。
1.2 T-SQL
1.2.1 TRY...CATCH
T-SQL中的TRY/CATCH用法和C++或C#中差不多,不过看上去没有Finally。。。
注意点1:在CATCH块处理完错误之后,会跳到END CATCH后的第一句。如果END CATCH已经是存储过程或触发器的最后一句了,那么就返回调用存储过程或触发器的那一句。在TRY块中所有出错之后的代码被忽视。
注意点2:severity(严重度)>=20,导致数据库引擎关闭连接的,不能被处理
注意点3:severity(严重度)<=10,视为警告或信息消息,,不能被处理
注意点4:要捕捉编译阶段或重编译阶段的错误,使用sp_executesql
1.2.2 ERROR 函数
T—SQL中要获得异常信息,先要加TRY...CATCH结构,然后在结构内使用ERROR_LINE, ERROR_MESSAGE, ERROR_NUMBER, ERROR_PROCEDURE, ERROR_SEVERITY 和 ERROR_STATE。看函数名就知道是派什么用的了。注意点1:如果在TRY...CATCH结构外使用就会返回NULL。(某个case中customer问为什么会返回NULL,估计就是这个原因。。。)
注意点2:CATCH块内不管被引用几次,不管在哪里引用,返回信息都一样。
注意点3:对于嵌套CATCH块,哪层的异常处理返回哪层的信息。
1.2.3 @@ERROR
捕捉之前运行的T-SQL语句的错误号,除了某个特例一般默认为0,对于11以上的message自增。对于SQL Server 2005之前的版本,如果要获得详细错误信息有些麻烦,但如果拥有sysadmin权限的话还是可能实现的(DBCC OUTPUTBUFFER)。一般不可能阻止SQL Server报错。
其中比较重要的11-16错误号代表的信息:
11 – Specified Database Object Not Found
12 – Unused
13 – User Transaction Syntax Error
14 – Insufficient Permission
15 – Syntax Error in SQL Statements
16 – Miscellaneous User Error
注意点1:@@ERROR与运行之后发生的错误没关系。
注意点2:如果是TRY块出错,@@ERROR要为CATCH的第一句;如果不是TRY块出错,@@ERROR要紧跟着出错的那一句。
注意点3:@@ERROR必须被保存到临时变量中才能对其进行操作
错误:
正确:
注意点4:另外相关的变量:@@rowcount(错误影响的行),@@trancount(事务数量)
1.2.4 Uncommittable Transactions(不可提交的事务)
TRY...CATCH中事务可能会打开但无法提交。当使用DDL或SET XACT_ABORT 设置为 ON 时都会进入该状态。
这里涉及到了用XACT_STATE 函数在CATCH块中测试事务状态。XACT_STATE有三种返回值:1为有正常活动事务,0为没有活动的用户事务,-1为有活动事务,但存在错误无法提交,需要被ROLLBACK TRANSACTION回滚。
1.2.5 Stored Procedure返回值
如果没有发生错误,那么返回值为0;如果发生错误,可能返回-1或0。
1.2.6 SET XACT_ABORT
SET XACT_ABORT OFF对应Statement-termination
SET XACT_ABORT ON对应Batch-abortion
XACT_ABORT应该是execution action aborted的缩写。
1.3 Handling Errors and Messages in Applications
针对ODBC,ADO,ADO.NET,SqlClient,OleDb处理方法也不同。
2. 异常发生的背后
针对不同错误SQL Server可能的应对方法也不一样。
可能采取的行动:
1. 语句终止(Statement-termination)。对于open transaction需要手动ROLLBACK TRANSACTION,不然之后的INSERT/UPDATE/DELETE不会自动回滚。
2. 范围中止(Scope-abortion)。A,B都为stored procedure,A调用B,B发生错误中止但A调用B之后的代码照常执行。通常限于编译错误。
3. Batch中止(Batch-abortion)。客户端提交到SQL Server的整个一批都中止,所有open transaction回滚。
4. 连接终止(Connection-termination)。open transaction回滚。@@ERROR不能获取值。通常是严重服务器端错误,除了一个例外:sp_OAxxxxx调用自定义stored procedure或OLE objects(应该是和DMO有关)。RAISERROR>=20的话也可以做到。
判断是语句终止还是batch中止有些困难,具体情况具体分析。
以下的两种情况不会(但没有官方说明)发生:
1. 事务回滚,但当前batch继续执行
2. batch中止,但事务不回滚。
有一种需要注意的是客户端的批取消(batch-cancellation)。客户端可以随时要求SQL Server中止执行batch,SQL Server可能会立即响应,也可能会延迟一会响应,且不会回滚事务(INSERT/UPDATE/DELETE/Trigger中会回滚)。更常见的场景是客户端timeout。ODBC, OLE DB, ADO 和 ADO.Net 都有30秒的默认timeout设置。可以通过设置 .CommandTimeout来避免这个问题。
SQL Server如何决定采取什么行动?除了特例SET XACT_ABORT OFF(这就是某位客户的关键问题所在,自从SQL Server 2005中说在触发器中这个特性取消了,明明还可以起作用的说。。。)和Trigger Context(那个客户的问题偏偏还是触发器中的SET XACT_ABORT OFF。。。)
对于Trigger Context,所有的错误都会终止batch并从这个节点开始回滚(例外是RAISERROR和error 266)。另外如果@@trancount为0(对于Trigger Context至少为1)时也会终止。所以Trigger内错误处理的模板应该为:
参考内容:
SQL Server BOL - Handling Database Engine Errors
http://www.sommarskog.se/error-handling-I.html
http://www.sommarskog.se/error-handling-II.html
http://rusanu.com/2007/10/31/error-handling-in-service-broker-procedures/
http://www.windows-tech.info/15/397cd9af617c7a4e.php