Tải bản đầy đủ (.pdf) (30 trang)

Errors and Exceptions

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (8.7 MB, 30 trang )

C H A P T E R 4

  


Errors and Exceptions
As software developers, we often daydream of a Utopian world of bug-free software—developed under
the shade of a palm tree on a remote island while sipping a long, fruity cocktail. But, alas, back in the real
world, hordes of developers sit in cubicle farms gulping acrid coffee, fighting bugs that are not always
their fault or under their control in any way.
Exceptions can occur in even the most stringently tested software, simply because it is not possible
to check every error condition in advance. For instance, do you know what will happen if a janitor, while
cleaning the data-center floor, accidentally slops some mop water into the fan enclosure of the database
server? It might crash, or it might not; it might just cause some component to fail somewhere deep in the
app, sending up a strange error message.
Although most exceptions won’t be so far out of the realm of testability, it is certainly important to
understand how to deal with them when and if they occur. It is also imperative that SQL Server
developers understand how to work with errors—both those thrown by the server itself and custom
errors built specifically for when problems occur during the runtime of an application.
Exceptions vs. Errors
The terms exception and error, while often used interchangeably by developers, actually refer to slightly
different conditions:
An error can occur if something goes wrong during the course of a program, even
though it can be purely informational in nature. For instance, the message
displayed by a program informing a user that a question mark is an invalid
character for a file name is considered to be an error message. However, this may
or may not mean that the program itself is in an invalid state.
An exception, on the other hand, is an error that is the result of an exceptional
circumstance. For example, if a network library is being used to send packets, and
the network connection is suddenly dropped due to someone unplugging a cable,
the library might throw an exception. An exception tells the calling code that


something went wrong and the routine aborted unexpectedly. If the caller does not
handle the exception (i.e., capture it), its execution will also abort. This process will
keep repeating until the exception is handled, or until it reaches the highest level of
the call stack, at which point the entire program will fail.
Another way to think about exceptions and errors is to think of errors as occurrences that are
expected by the program. The error message that is displayed when a file name contains an invalid
character is informational in nature because the developer of the program predicted that such an event
would occur and created a code path specifically to deal with it. A dropped network connection, on the
71
CHAPTER 4  ERRORS AND EXCEPTIONS
other hand, could be caused by any number of circumstances and therefore is much more difficult to
handle specifically. Instead, the solution is to raise an exception and fail. The exception can then be
handled by a routine higher in the call stack, which can decide what course of action to take in order to
solve the problem.
What Defines an "Exceptional" Circumstance?
There is some debate in the software community as to whether exceptions should really be used only for
exceptional circumstances, or whether it is acceptable to use them as part of the regular operation of a
program. For example, some programmers choose to define a large number of different custom
exceptions, and then deliberately raise these exceptions as a method of controlling the flow of an
application. There are some possible benefits to this approach: raising an exception is a useful way to
break out of a routine immediately, however deeply nested in the call stack that routine might be; and, an
exception can be used to describe conditions that would be difficult to express in the normal return value
of a method. However convenient it may seem to use exceptions in such scenarios, they clearly represent
an abuse of the intended purpose of exception-handling code (which, remember, is to deal with
exceptional circumstances). Furthermore, making lots of exception calls can make your code hard to read,
and debugging can be made more difficult, since debuggers generally implement special behavior
whenever exceptions are encountered. As a result, exception-laden code is more difficult to maintain when
compared to code that relies on more standard control flow structures.
In my opinion, you should raise exceptions only to describe truly exceptional circumstances. Furthermore,
due to the fact that exceptions can cause abort conditions, they should be used sparingly. However, there

is certainly an upside to using exceptions over errors, which is that it’s more difficult for the caller to ignore
an exception, since it will cause code to abort if not properly handled. If you’re designing an interface that
needs to ensure that the caller definitely sees a certain condition when and if it occurs, it might make
sense to use an exception rather than an error.
As in almost all cases, the decision is very dependent on the context of your application, so do not feel
obliged to stick to my opinion!
How Exceptions Work in SQL Server
The first step in understanding how to handle errors and exceptions in SQL Server is to take a look at
how the server itself deals with error conditions. Unlike many other programming languages, SQL Server
has an exception model that involves different behaviors for different types of exceptions. This can cause
unexpected behavior when error conditions do occur, so careful programming is essential when dealing
with T-SQL exceptions.
To begin with, think about connecting to a SQL Server and issuing some T-SQL. First, you must
establish a connection to the server by issuing login credentials. The connection also determines what
database will be used as the default for scope resolution (i.e., finding objects—more on this in a bit).
Once connected, you can issue a batch of T-SQL. A batch consists of one or more T-SQL statements,
which will be compiled together to form an execution plan.
72
Download at WoweBook.com
CHAPTER 4  ERRORS AND EXCEPTIONS
The behavior of the exceptions thrown by SQL Server mostly follows this same pattern: depending
on the type of exception, a statement, a batch, or an entire connection may be aborted. Let’s take a look
at some practical examples to see this in action.
Statement-Level Exceptions
A statement-level exception aborts only the current statement that is running within a batch of T-SQL,
allowing any subsequent statements within the batch to run. To see this behavior, you can use SQL
Server Management Studio to execute a batch that includes an exception, followed by a PRINT statement.
For instance:
SELECT POWER(2, 32);
PRINT 'This will print!';

GO
Running this batch results in the following output:
Msg 232, Level 16, State 3, Line 1
Arithmetic overflow error for type int, value = 4294967296.000000.
This will print!
When this batch was run, the attempt to calculate POWER(2, 32) caused an integer overflow, which
threw the exception. However, only the SELECT statement was aborted. The rest of the batch continued
to run, which, in this case, meant that the PRINT statement still printed its message.
Batch-Level Exceptions
Unlike a statement-level exception, a batch-level exception does not allow the rest of the batch to
continue running. The statement that throws the exception will be aborted, and any remaining
statements in the batch will not be run. An example of a batch-aborting exception is an invalid
conversion, such as the following:
SELECT CONVERT(int, 'abc');
PRINT 'This will NOT print!';
GO
The output of this batch is as follows:
Msg 245, Level 16, State 1, Line 1
Conversion failed when converting the varchar value 'abc' to data type int.
73
CHAPTER 4  ERRORS AND EXCEPTIONS
In this case, the conversion exception occurred in the SELECT statement, which aborted the batch at
that point. The PRINT statement was not allowed to run, although if the batch had contained any valid
statements before the exception, these would have been executed successfully.
Batch-level exceptions might be easily confused with connection-level exceptions (which drop the
connection to the server), but after a batch-level exception, the connection is still free to send other
batches. For instance:
SELECT CONVERT(int, 'abc');
GO
PRINT 'This will print!';

GO
In this case there are two batches sent to SQL Server, separated by the batch separator, GO. The first
batch throws a conversion exception, but the second batch is still run. This results in the following
output:
Msg 245, Level 16, State 1, Line 2
Conversion failed when converting the varchar value 'abc' to data type int.
This will print!
Batch-level exceptions do not affect only the scope in which the exception occurs. The exception
will bubble up to the next level of execution, aborting every call in the stack. This can be illustrated by
creating the following stored procedure:
CREATE PROCEDURE ConversionException
AS
BEGIN
SELECT CONVERT(int, 'abc');
END;
GO
Running this stored procedure followed by a PRINT shows that, even when an exception occurs in an
inner scope (within the stored procedure), the outer batch is still aborted:
EXEC ConversionException;
PRINT 'This will NOT print!';
GO
The result of this batch is the same as if no stored procedure was used:
Msg 245, Level 16, State 1, Line 4
Conversion failed when converting the varchar value 'abc' to data type int.
74
CHAPTER 4  ERRORS AND EXCEPTIONS
Parsing and Scope-Resolution Exceptions
Exceptions that occur during parsing or during the scope-resolution phase of compilation appear at first
to behave just like batch-level exceptions. However, they actually have a slightly different behavior. If the
exception occurs in the same scope as the rest of the batch, these exceptions will behave just like a

batch-level exception. If, on the other hand, an exception occurs in a lower level of scope, these
exceptions will behave just like statement-level exceptions—at least, as far as the outer batch is
concerned.
As an example, consider the following batch, which includes a malformed SELECT statement (this is a
parse exception):
SELECTxzy FROM SomeTable;
PRINT 'This will NOT print!';
GO
In this case, the PRINT statement is not run, because the whole batch is discarded during the parse
phase. The output is the following exception message:
Msg 156, Level 15, State 1, Line 1
Incorrect syntax near the keyword 'FROM'.
To see the difference in behavior, the SELECT statement can be executed as dynamic SQL using the
EXEC function. This causes the SELECT statement to execute in a different scope, changing the exception
behavior from batch-like to statement-like. Try running the following T-SQL to observe the change:
EXEC('SELECTxzy FROM SomeTable');
PRINT 'This will print!';
GO
The PRINT statement is now executed, even though the exception occurred:
Msg 156, Level 15, State 1, Line 1
Incorrect syntax near the keyword 'FROM'.
This will print!
This type of exception behavior also occurs during scope resolution. Essentially, SQL Server
processes queries in two phases. The first phase parses and validates the query and ensures that the T-
SQL is well formed. The second phase is the compilation phase, during which an execution plan is built
and objects referenced in the query are resolved. If a query is submitted to SQL Server via ad hoc SQL
from an application or dynamic SQL within a stored procedure, these two phases happen together.
However, within the context of stored procedures, SQL Server exploits late binding. This means that the
parse phase happens when the stored procedure is created, and the compile phase (and therefore scope
resolution) occurs only when the stored procedure is executed.

75
CHAPTER 4  ERRORS AND EXCEPTIONS
To see what this means, create the following stored procedure (assuming that a table called
SomeTable does not exist in the current database):
CREATE PROCEDURE NonExistentTable
AS
BEGIN
SELECT xyz
FROM SomeTable;
END;
GO
Although SomeTable does not exist, the stored procedure is created—the T-SQL parses without any
errors. However, upon running the stored procedure, an exception is thrown:
EXEC NonExistentTable;
GO
This leads to
Msg 208, Level 16, State 1, Procedure NonExistentTable, Line 4
Invalid object name 'SomeTable'.
Like the parse exception, scope-resolution exceptions behave similarly to batch-level exceptions
within the same scope, and similarly to statement-level exceptions in the outer scope. Since the stored
procedure creates a new scope, hitting this exception within the procedure aborts the rest of the
procedure, but any T-SQL encountered in the calling batch after execution of the procedure will still run.
For instance:
EXEC NonExistentTable;
PRINT 'This will print!';
GO
leads to the following result:
Msg 208, Level 16, State 1, Procedure NonExistentTable, Line 4
Invalid object name 'SomeTable'.
This will print!

Connection and Server-Level Exceptions
Some exceptions thrown by SQL Server can be so severe that they abort the entire connection, or cause
the server itself to crash. These types of connection- and server-level exceptions are generally caused by
internal SQL Server bugs, and are thankfully quite rare. At the time of writing, I cannot provide any
76
CHAPTER 4  ERRORS AND EXCEPTIONS
examples of these types of exceptions, as I am not aware of any reproducible conditions in SQL Server
2008 that cause them.
The XACT_ABORT Setting
Although users do not have much control over the behavior of exceptions thrown by SQL Server, there is
one setting that can be modified on a per-connection basis. Turning on the XACT_ABORT setting makes all
statement-level exceptions behave like batch-level exceptions. This means that control will always be
immediately returned to the client any time an exception is thrown by SQL Server during execution of a
query (assuming the exception is not handled).
To enable XACT_ABORT for a connection, the following T-SQL is used:
SET XACT_ABORT ON;
This setting will remain enabled for the entire connection—even if it was set in a lower level of
scope, such as in a stored procedure or dynamic SQL—until it is disabled using the following T-SQL:
SET XACT_ABORT OFF;
To illustrate the effect of this setting on the behavior of exceptions, let’s review a couple of the
exceptions already covered. Recall that the following integer overflow exception operates at the
statement level:
SELECT POWER(2, 32);
PRINT 'This will print!';
GO
Enabling the XACT_ABORT setting before running this T-SQL changes the output, resulting in the
PRINT statement not getting executed:
SET XACT_ABORT ON;
SELECT POWER(2, 32);
PRINT 'This will NOT print!';

GO
The output from running this batch is as follows:
Msg 232, Level 16, State 3, Line 2
Arithmetic overflow error for type int, value = 4294967296.000000.
Note that XACT_ABORT only affects the behavior of runtime errors, not those generated during
compilation. Recall the previous example that demonstrated a parsing exception occurring in a lower
scope using the EXEC function:
EXEC('SELECTxzy FROM SomeTable');
PRINT 'This will print!';
GO
The result of this code listing will remain the same, regardless of the XACT_ABORT setting, resulting in
the PRINT statement being evaluated even after the exception occurs.
77
CHAPTER 4  ERRORS AND EXCEPTIONS
In addition to controlling exception behavior, XACT_ABORT also modifies how transactions behave
when exceptions occur. See the section “Transactions and Exceptions” later in this chapter for more
information.
Dissecting an Error Message
A SQL Server exception has a few different component parts, each of which are represented within the
text of the error message. Each exception has an associated error number, error level, and state. Error
messages can also contain additional diagnostic information including line numbers and the name of
the procedure in which the exception occurred.
Error Number
The error number of an exception is listed following the text Msg within the error text. For example, the
error number of the following exception is 156:
Msg 156, Level 15, State 1, Line 1
Incorrect syntax near the keyword 'FROM'.
SQL Server generally returns the error message with the exception, so having the error number
usually doesn’t assist from a problem-solving point of view. However, there are times when knowing the
error number can be of use. Examples include use of the @@ERROR function, or when doing specialized

error handling using the TRY/CATCH syntax (see the sections “Exception ‘Handling’ Using @@ERROR” and
“SQL Server’s TRY/CATCH Syntax” later in the chapter for details on these topics).
The error number can also be used to look up the localized translation of the error message from the
sys.messages catalog view. The message_id column contains the error number, and the language_id
column can be used to get the message in the correct language. For example, the following T-SQL
returns the English text for error 208:
SELECT text
FROM sys.messages
WHERE
message_id = 208
AND language_id = 1033;
GO
The output of this query is an error message template, shown here:
Invalid object name '%.*ls'.
See the section “SQL Server’s RAISERROR Function” for more information about error message
templates.
78
CHAPTER 4  ERRORS AND EXCEPTIONS
Error Level
The Level tag within an error message indicates a number between 1 and 25. This number can
sometimes be used to either classify an exception or determine its severity. Unfortunately, the key word
is “sometimes”: the error levels assigned by SQL Server are highly inconsistent and should generally not
be used in order to make decisions about how to handle exceptions.
The following exception, based on its error message, is of error level 15:
Msg 156, Level 15, State 1, Line 1
Incorrect syntax near the keyword 'FROM'.
The error levels for each exception can be queried from the sys.messages view, using the severity
column. A severity of less than 11 indicates that a message is a warning. If severity is 11 or greater, the
message is considered to be an error and can be broken down into the following documented
categories:

• Error levels 11 through 16 are documented as “errors that can be corrected by the
user.” The majority of exceptions thrown by SQL Server are in this range,
including constraint violations, parsing and compilation errors, and most other
runtime exceptions.
• Error levels 17 through 19 are more serious exceptions. These include out-of-
memory exceptions, disk space exceptions, internal SQL Server errors, and other
similar violations. Many of these are automatically logged to the SQL Server error
log when they are thrown. You can identify those exceptions that are logged by
examining the is_event_logged column of the sys.messages table.
• Error levels 20 through 25 are fatal connection and server-level exceptions. These
include various types of data corruption, network, logging, and other critical
errors. Virtually all of the exceptions at this level are automatically logged.
Although the error levels that make up each range are individually documented in Books Online
( this information is inconsistent or
incorrect in many cases. For instance, according to documentation, severity level 11 indicates errors
where “the given object or entity does not exist.” However, error 208, “Invalid object name,” is a level-16
exception. Many other errors have equally unpredictable levels, and it is recommended that you do not
program client software to rely on the error levels for handling logic.
In addition to inconsistency regarding the relative severity of different errors, there is, for the most
part, no discernable pattern regarding the severity level of an error and whether that error will behave on
the statement or batch level. For instance, both errors 245 (“Conversion failed”) and 515 (“Cannot insert
the value NULL . . . column does not allow nulls”) are level-16 exceptions. However, 245 is a batch-level
exception, whereas 515 acts at the statement level.
Error State
Each exception has a State tag, which contains information about the exception that is used internally
by SQL Server. The values that SQL Server uses for this tag are not documented, so this tag is generally
not helpful. The following exception has a state of 1:
79
CHAPTER 4  ERRORS AND EXCEPTIONS
Msg 156, Level 15, State 1, Line 1

Incorrect syntax near the keyword 'FROM'.
Additional Information
In addition to the error number, level, and state, many errors also carry additional information about the
line number on which the exception occurred and the procedure in which it occurred, if relevant. The
following error message indicates that an invalid object name was referenced on line 4 of the procedure
NonExistentTable:
Msg 208, Level 16, State 1, Procedure NonExistentTable, Line 4
Invalid object name 'SomeTable'.
If an exception does not occur within a procedure, the line number refers to the line in the batch in
which the statement that caused the exception was sent.
Be careful not to confuse batches separated with GO with a single batch. Consider the following T-
SQL:
SELECT 1;
GO
SELECT 2;
GO
SELECT 1/0;
GO
In this case, although a divide-by-zero exception occurs on line 5 of the code listing itself, the
exception message will report that the exception was encountered on line 1:
(1 row(s) affected)

(1 row(s) affected)
Msg 8134, Level 16, State 1, Line 1
Divide by zero error encountered.
The reason for the reset of the line number is that GO is not actually a T-SQL command. GO is an
identifier recognized by SQL Server client tools (e.g., SQL Server Management Studio and SQLCMD) that
tells the client to separate the query into batches, sending each to SQL Server one after another. This
seemingly erroneous line number reported in the previous example occurs because each batch is sent
separately to the query engine. SQL Server does not know that on the client (e.g., in SQL Server

Management Studio) these batches are all displayed together on the screen. As far as SQL Server is
80
CHAPTER 4  ERRORS AND EXCEPTIONS
concerned, these are three completely separate units of T-SQL that happen to be sent on the same
connection.
SQL Server’s RAISERROR Function
In addition to the exceptions that SQL Server itself throws, users can raise exceptions within T-SQL by
using a function called RAISERROR. The general form for this function is as follows:
RAISERROR ( { msg_id | msg_str | @local_variable }
{ ,severity ,state }
[ ,argument [ ,...n ] ] )
[ WITH option [ ,...n ] ]
The first argument can be an ad hoc message in the form of a string or variable, or a valid error
number from the message_id column of sys.messages. If a string is specified, it can include format
designators that can then be filled using the optional arguments specified at the end of the function call.
The second argument, severity, can be used to enforce some level of control over the behavior of
the exception, similar to the way in which SQL Server uses error levels. For the most part, the same
exception ranges apply: exception levels between 1 and 10 result in a warning, levels between 11 and 18
are considered normal user errors, and those above 18 are considered serious and can only be raised by
members of the sysadmin fixed-server role. User exceptions raised over level 20, just like those raised by
SQL Server, cause the connection to break. Beyond these ranges, there is no real control afforded to
user-raised exceptions, and all are considered to be statement level—this is even true with XACT_ABORT
set.
 Note
XACT_ABORT
does not impact the behavior of the
RAISERROR
statement.
The state argument can be any value between 1 and 127, and has no effect on the behavior of the
exception. It can be used to add additional coded information to be carried by the exception—but it’s

probably just as easy to add that data to the error message itself in most cases.
The simplest way to use RAISERROR is to pass in a string containing an error message, and set the
appropriate error level and state. For general exceptions, I usually use severity 16 and a value of 1 for
state:
RAISERROR('General exception', 16, 1);
This results in the following output:
Msg 50000, Level 16, State 1, Line 1
General exception
Note that the error number generated in this case is 50000, which is the generic user-defined error
number that will be used whenever passing in a string for the first argument to RAISERROR.
81
CHAPTER 4  ERRORS AND EXCEPTIONS
 Caution Previous versions of SQL Server allowed RAISERROR syntax specifying the error number and message
number as follows:
RAISERROR 50000 'General exception'
. This syntax is deprecated in SQL Server 2008 and
should not be used.
Formatting Error Messages
When defining error messages, it is generally useful to format the text in some way. For example, think
about how you might write code to work with a number of product IDs, dynamically retrieved, in a loop.
You might have a local variable called @ProductId, which contains the ID of the product that the code is
currently working with. If so, you might wish to define a custom exception that should be thrown when a
problem occurs—and it would probably be a good idea to return the current value of @ProductId along
with the error message.
In this case, there are a couple of ways of sending back the data with the exception. The first is to
dynamically build an error message string:
DECLARE @ProductId int;
SET @ProductId = 100;

/* ... problem occurs ... */


DECLARE @ErrorMessage varchar(200);
SET @ErrorMessage =
'Problem with ProductId ' + CONVERT(varchar, @ProductId);

RAISERROR(@ErrorMessage, 16, 1);
Executing this batch results in the following output:
Msg 50000, Level 16, State 1, Line 10
Problem with ProductId 100
While this works for this case, dynamically building up error messages is not the most elegant
development practice. A better approach is to make use of a format designator and to pass @ProductId as
an optional parameter, as shown in the following code listing:
DECLARE @ProductId int;
SET @ProductId = 100;

/* ... problem occurs ... */

RAISERROR('Problem with ProductId %i', 16, 1, @ProductId);
Executing this batch results in the same output as before, but requires quite a bit less code, and you
don’t have to worry about defining extra variables or building up messy conversion code. The %i
82

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×