Académique Documents
Professionnel Documents
Culture Documents
2005
Takeaway: SQL Server 2005 offers new features that enhance productivity,
efficiency, availability, and administrative ease. Tim Chapman discusses 10 of the more
advanced new features of the SQL Server 2005 Database Engine.
SQL Server 2005 offers a plethora of new features and enhancements that improve
productivity, efficiency, availability, and administrative ease. In this article, I discuss 10
of the more advanced features of the SQL Server 2005 Database Engine.
1. Database mirroring
Database mirroring is a new high-availability feature in SQL Server 2005. It's similar to
server clustering in that failover is achieved by the use of a stand-by server; the difference
is that the failover is at the database level rather than the server level. The primary
database continuously sends transaction logs to the backup database on a separate SQL
Server instance. A third SQL Server instance is then used as a witness database to
monitor the interaction between the primary and the mirror databases.
2. Database snapshots
A database snapshot is essentially an instant read-only copy of a database, and it is a great
candidate for any type of reporting solution for your company. In addition to being a
great reporting tool, you can revert control from your primary database to your snapshot
database in the event of an error. The only data loss would be from the point of creation
of the database snapshot to the event of failure.
3. CLR integration
With SQL Server 2005, you now have the ability to create custom .NET objects with the
database engine. For example, stored procedures, triggers, and functions can now be
created using familiar .NET languages such as VB and C#. Exposing this functionality
gives you tools that you never had access to before such as regular expressions.
4. Service Broker
This feature gives you the ability to create asynchronous, message-based applications in
the database entirely through TSQL. The database engine guarantees message delivery,
message order consistency, and handles message grouping. In addition, Service Broker
gives you the ability to send messages between different SQL Server instances. Server
Broker is also used in several other features in SQL Server 2005. For example, you can
define Event Nonfictions in the database to send a message to a Queue in the database
when someone attempts to alter a table structure, of if there is a string of login failures.
5. DDL triggers
In previous articles, I outlined how you can use data definition language (DDL) triggers
in SQL Server 2005 to implement custom database and server auditing solutions for
Sarbanes-Oxley compliance (here are part one and part two of my SOX articles). DDL
triggers are defined at the server or database level and fire when DDL statements occur.
This gives you the ability to audit when new tables, stored procedures, or logins are
created.
6. Ranking functions
SQL Server 2005 provides you with the ability to rank result sets returned from the
database engine. This allows you to customize the manner in which result sets are
returned, such as creating customized paging functions for Web site data.
The database engine is able to guarantee the consistency through row versions stored in
the tempdb database. When a statement or transaction is issued with their respective
isolation levels, read operations accessing the same data that is being involved in a
transaction will read from the previous version of the data that is stored in tempdb. Using
these techniques in the appropriate situations can significantly decrease your database
locking issues.
8. XML integration
SQL Server 2005 introduces the new XML data-type. You can store full XML documents
in this new data-type, and you can place validations on the well-formed documents in the
database. Additional enhancements include the ability to query the XML documents and
create indexes on the XML data-type.
9. TRY...CATCH
In a previous article, I outlined how you can use the new TRY...CATCH constructs in
SQL Server 2005 to catch and handle deadlocks when they occur in the database. This
long-awaited feature simplifies error handling in the database.
Try/Catch consists of two blocks—Try and Catch—each of which can contain multiple
statements. Listing A contains a simple example in pseudo-code.
If either the insert or the update fails, the implicit transaction is rolled back. The insert
statement cannot succeed (i.e., it is committed) while the update statement fails. Both
succeed or both fail.
(Note: Any error that is caught by the Catch block is not returned to the caller.)
With SQL Server 2000, any fatal error (i.e., an error with a severity level greater than 17)
immediately terminates execution and breaks the connection. With SQL Server 2005, the
engine will attempt to handle the error and continue execution. Listing B illustrates this
point.
When you paste the code from Listing B into a new query window in Management
Studio and execute it, the results look like this:
Begin Try
Begin Catch
Variable @int = 0
Code Completed
The Try block attempts to divide by zero, which immediately invokes the Catch block;
this prints its message and then assigns zero to the variable. Code execution continues,
printing the final two statements.
• Error_Message(): returns the error message that would typically be sent to the
caller.
• Error_Number(): returns the integer assigned to the error.
• Error_Severity(): returns the integer corresponding to the severity.
• Error_State(): returns the state.
• Error_Procedure(): returns the name of the procedure or function where the
error occurred.
• Error_Line(): returns the line number where the error occurred.
To demonstrate the functions' use, I revised the code, which you can see in Listing C.
I fancied up the custom error message so it displays on multiple lines for clarity. (You
might want to keep this code for use as a template Catch block in the future.) This results
in the following display:
Begin Try
Begin Catch
Msg 50000, Level 16, State 2, Line 20
Error(8134): Divide by zero error encountered.
Severity: 16
State: 1
Procedure: None.
Line #: 4
Variable @int = 0
Code Completed
Note: Application code would typically be using transactions. The mere existence of a
Catch block won't automatically perform a rollback—you must do it yourself. In this
case, your procedure would resemble this generic version:
BEGIN TRY
BEGIN TRANSACTION
[Do some stuff here]
COMMIT TRANSACTION
END TRY
BEGIN CATCH
Print 'Begin Catch'
ROLLBACK TRANSACTION
[Do some stuff here]
END CATCH
Error handling has come a long way in SQL Server 2005. If you're still handling errors
the old way, with nothing more than Raiserror() in your toolkit, it's time to wake up and
smell the coffee!
Miss a tip?
Check out the SQL Server archive, and catch up on the most recent editions of Arthur
Fuller's column.
TechRepublic's free SQL Server newsletter, delivered each Tuesday, contains hands-on
tips that will help you become more adept with this powerful relational database
management system. Automatically subscribe today!
Most iterative language compilers have built-in error handling routines (e.g., TRY…
CATCH statements) that developers can leverage when designing their code. Although
SQL Server 2000 developers don't enjoy the luxury that iterative language developers do
when it comes to built-in tools, they can use the @@ERROR system variable to design
their own effective error-handling tools.
Introducing transactions
In order to grasp how error handling works in SQL Server 2000, you must first
understand the concept of a database transaction. In database terms, a transaction is a
series of statements that occur as a single unit of work. To illustrate, suppose you have
three statements that you need to execute. The transaction can be designed in such a way
so that all three statements occur successfully, or none of them occur at all.
When data manipulation operations are performed in SQL Server, the operation takes
place in buffer memory and not immediately to the physical table. Later, when the
CHECKPOINT process is run by SQL Server, the committed changes are written to disk.
This means that when transactions are occurring, the changes are not made to disk during
the transaction, and are never written to disk until committed. Long-running transactions
require more processing memory and require that the database hold locks for a longer
period of time. Thus, you must be careful when designing long running transactions in a
production environment.
Here's a good example of how using transactions is useful. Withdrawing money from an
ATM requires a series of steps which include entering a PIN number, selecting an account
type, and entering the amount of funds you wish to withdraw. If you try to withdraw $50
from the ATM and the machine fails thereafter, you do not want to be charged the $50
without receiving the money. Transactions can be used to ensure this consistency.
Successful error handling in SQL Server 2000 requires consistently checking the value of
the @@ERROR system variable. @@ERROR is a variable updated by the SQL Server
database engine after each statement is executed on the server for the given connection.
This variable contains the corresponding error number, if applicable. You can find a
listing of these error numbers in the sysmessages table in the master database. The details
of this table are listed on Microsoft's site.
PRINT @@ERROR
In these instructions, we are printing out a string to the screen and printing the value of
the @@ERROR variable. Because no error is returned from printing out to the screen,
the value @@ERROR contains is 0.
PRINT 1/0
PRINT @@ERROR
In this example, we generate a division by zero error, which means that the @@ERROR
variable will contain 8134, which is the error number that Microsoft assigns for this type
of error. For most error handling purposes, you will only be concerned if the value of
@@ERROR is non-zero, which will indicate that an error occurred. It is a good idea to
keep track of the error numbers when recording the errors as they will come in handy
during the debugging process.
Execute the following statement to create the table that we will use for our example:
(
TranID SMALLINT IDENTITY(1,1) PRIMARY KEY,
EntryDate SMALLDATETIME DEFAULT(GETDATE()),
ParamValue CHAR(1),
ThrowError BIT
The two fields of value in the script are ParamValue and ThrowError. These fields will
correspond to the input parameters of the procedure we will create, and we will use them
in our logic for committing transactions.
Once our table is in place to keep track of our transactions, we are ready to create our
procedure. The procedure will have a parameter used simply to record a character value
and a parameter, which will give us the ability to throw an error in the procedure. Run the
statement in Listing A to create the procedure.
This simple stored procedure exhibits the characteristics we need for effective error
handling. First, a transaction is explicitly declared. After a record is inserted into the
Transaction table, we check the value of the @ThrowError parameter. This parameter
indicates whether to throw an error, and uses the RAISERROR function to throw the
custom error. When the RAISERROR function is called, the value of the @@ERROR
variable is populated with the error number that we provide.
If an error occurs in the stored procedure, we will roll back the transaction. Rolling back
the transactions means that the record we attempted to insert into the Transactions table
will be removed as if it never occurred. The state of the database will be exactly how it
was before the transaction began.
In this example, you will also notice the use of the GOTO statement and the label
ErrorHandler. GOTO statements are typically considered a bad programming practice in
iterative programming languages, but they are very useful when handling errors in SQL
Server 2000. Don't be afraid to use the GOTO statement to handle errors.
This procedure call will throw an error and the record will not be inserted in the
Transactions table:
PRINT @ReturnCode
This procedure call will not throw an error, and the inserted record will be committed to
the Transactions table:
PRINT @ReturnCode
These procedure calls make use of a Return parameter, which indicates the success or
failure of a stored procedure. It is a good programming practice to explicitly set the
Return parameter in your code to indicate success or failure of the procedure; this allows
you to know when your stored procedure has failed so you can take the necessary steps to
handle the failure. For example, you can nest procedure calls and transactions. Your
application could potentially declare a transaction, call a stored procedure, and
(depending on the success or failure of the stored procedure) commit or roll back the
outside transaction.
With SQL Server 2005, Microsoft gives us newer, more robust error handling capabilities
by adding Try...Catch blocks to T-SQL. In this tip, we will look at how Try...Catch blocks
work and how you can put them to use in your code. We will also look at some
comparisons between the old ways and the new ways so you'll have a better
understanding of how using Try...Catch can bring your error handling into the 21st
century.
Try...Catch defined
BEGIN TRY
[T-SQL Code Goes Here]
END TRY
BEGIN CATCH
[Exception Handling Code Goes Here]
END CATCH
When using Try…Catch in T-SQL, here are a few crucial points to keep in mind:
Try blocks must be followed directly by a Catch block, anything else will cause an
error.
Try…Catch cannot span batches.
If the Try code runs without fail, execution passes to the first line following the end of
the Catch block.
When Catch code completes, execution passes to the first line following the end of the
Catch block.
Handling errors
When an error occurs, you as the developer need to decide how to deal with it. Since you
cannot pass control back to code that caused the error, like you can in .NET languages,
you will likely log the problem and roll back any transactions that may be in-flight. To
help with logging, there are several system functions that can provide more information
about an error. The available system functions are detailed below:
Using these functions, you can record the details of the error and return that information
to the caller of the procedure or log it for troubleshooting purposes. The functions will
only work when called inside a Catch block; trying to call them otherwise would return
NULL. They will, however, work within the scope of the code running in the Catch
block. This means that you can call a stored procedure to handle the error and that stored
procedure can access the error functions. Here is an example of just such a stored
procedure:
This procedure can then be called in your Catch block to return and log the error details.
BEGIN TRY
--INSERT CODE HERE: When an error occurs, control will be passed
to the Catch block
END TRY
BEGIN CATCH
--This proc will log the error and send the details back to the
calling application
EXEC spLogError
END CATCH
Using the XACT_STATE() function
There is one more error function that we have not discussed, the XACT_STATE()
function. This function can be called in your Catch blocks to return the exact transaction
state of your procedure. The return code from XACT_STATE() will have one of the
following values:
Based on the return of this function, you can handle your in-flight transaction. If you
receive a 1, you can roll back or commit as normal. If you receive a 0, there are no open
transactions, and attempting a commit would generate an error. The special case is -1.
That means there was a transaction, but it is not committable. You are also unable to roll
back to a save point when you receive a -1 code; the entire transaction must be rolled
back.
Error and exception handling is crucial to any good coding standards. Now with SQL
Server 2005 you can implement advanced error handling using Try…Catch blocks.
TRY...CATCH (Transact-SQL)
Implements error handling for Transact-SQL that is similar to the exception handling in the Microsoft
Visual C# and Microsoft Visual C++ languages. A group of Transact-SQL statements can be enclosed
in a TRY block. If an error occurs in the TRY block, control is passed to another group of statements
that is enclosed in a CATCH block.
Syntax
BEGIN TRY
{ sql_statement | statement_block }
END TRY
BEGIN CATCH
[ { sql_statement | statement_block } ]
END CATCH
[ ; ]
Arguments
sql_statement
Is any Transact-SQL statement.
statement_block
Any group of Transact-SQL statements in a batch or enclosed in a BEGIN…END block.
Remarks
A TRY…CATCH construct catches all execution errors that have a severity higher than 10 that do not
close the database connection.
A TRY block must be immediately followed by an associated CATCH block. Including any other
statements between the END TRY and BEGIN CATCH statements generates a syntax error.
A TRY…CATCH construct cannot span multiple batches. A TRY…CATCH construct cannot span multiple
blocks of Transact-SQL statements. For example, a TRY…CATCH construct cannot span two BEGIN…
END blocks of Transact-SQL statements and cannot span an IF…ELSE construct.
If there are no errors in the code that is enclosed in a TRY block, when the last statement in the TRY
block has finished running, control passes to the statement immediately after the associated END
CATCH statement. If there is an error in the code that is enclosed in a TRY block, control passes to
the first statement in the associated CATCH block. If the END CATCH statement is the last statement
in a stored procedure or trigger, control is passed back to the statement that called the stored
procedure or fired the trigger.
When the code in the CATCH block finishes, control passes to the statement immediately after the
END CATCH statement. Errors trapped by a CATCH block are not returned to the calling application.
If any part of the error information must be returned to the application, the code in the CATCH block
must do so by using mechanisms such as SELECT result sets or the RAISERROR and PRINT
statements. For more information about how to use RAISERROR with TRY…CATCH, see Using
TRY...CATCH in Transact-SQL.
TRY…CATCH constructs can be nested. Either a TRY block or a CATCH block can contain nested TRY…
CATCH constructs. For example, a CATCH block can contain an embedded TRY…CATCH construct to
handle errors encountered by the CATCH code.
Errors encountered in a CATCH block are treated like errors generated anywhere else. If the CATCH
block contains a nested TRY…CATCH construct, any error in the nested TRY block will pass control to
the nested CATCH block. If there is no nested TRY…CATCH construct, the error is passed back to the
caller.
TRY…CATCH constructs catch unhandled errors from stored procedures or triggers executed by the
code in the TRY block. Alternatively, the stored procedures or triggers can contain their own TRY…
CATCH constructs to handle errors generated by their code. For example, when a TRY block
executes a stored procedure and an error occurs in the stored procedure, the error can be handled
in the following ways:
• If the stored procedure does not contain its own TRY…CATCH construct, the error returns
control to the CATCH block associated with the TRY block that contains the EXECUTE
statement.
• If the stored procedure contains a TRY…CATCH construct, the error transfers control to the
CATCH block in the stored procedure. When the CATCH block code finishes, control is
passed back to the statement immediately after the EXECUTE statement that called the
stored procedure.
GOTO statements cannot be used to enter a TRY or CATCH block. GOTO statements can be used to
jump to a label inside the same TRY or CATCH block or to leave a TRY or CATCH block.
In the scope of a CATCH block, the following system functions can be used to obtain information
about the error that caused the CATCH block to be executed:
• ERROR_PROCEDURE() returns the name of the stored procedure or trigger where the error
occurred.
• ERROR_LINE() returns the line number inside the routine that caused the error.
• ERROR_MESSAGE() returns the complete text of the error message. The text includes the
values supplied for any substitutable parameters, such as lengths, object names, or times.
These functions return NULL if they are called outside the scope of the CATCH block. Error
information can be retrieved by using these functions from anywhere within the scope of the CATCH
block. For example, the following script shows a stored procedure that contains error-handling
functions. In the CATCH block of a TRY…CATCH construct, the stored procedure is called and
information about the error is returned.
Copy Code
USE AdventureWorks;
GO
GO
AS
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;
GO
BEGIN TRY
SELECT 1/0;
END TRY
BEGIN CATCH
EXECUTE usp_GetErrorInfo;
END CATCH;
processing for the session. If an error occurs that has severity of 20 or higher and the
database connection is not disrupted, TRY…CATCH will handle the error.
• When the session is ended by a system administrator by using the KILL statement.
The following types of errors are not handled by a CATCH block when they occur at the same level
of execution as the TRY…CATCH construct:
• Compile errors, such as syntax errors, that prevent a batch from running.
• Errors that occur during statement-level recompilation, such as object name resolution
These errors are returned to the level that ran the batch, stored procedure, or trigger.
If an error occurs during compilation or statement-level recompilation at a lower execution level (for
example, when executing sp_executesql or a user-defined stored procedure) inside the TRY block,
the error occurs at a lower level than the TRY…CATCH construct and will be handled by the
associated CATCH block. For more information, see Using TRY...CATCH in Transact-SQL.
The following example shows how an object name resolution error generated by a SELECT
statement is not caught by the TRY…CATCH construct, but is caught by the CATCH block when the
same SELECT statement is executed inside a stored procedure.
Copy Code
USE AdventureWorks;
GO
BEGIN TRY
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() as ErrorNumber,
ERROR_MESSAGE() as ErrorMessage;
END CATCH
The error is not caught and control passes out of the TRY…CATCH construct to the next higher level.
Running the SELECT statement inside a stored procedure will cause the error to occur at a level
lower than the TRY block. The error will be handled by the TRY…CATCH construct.
Copy Code
GO
AS
GO
BEGIN TRY
EXECUTE usp_ExampleProc
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() as ErrorNumber,
ERROR_MESSAGE() as ErrorMessage;
END CATCH;
If an error generated in a TRY block causes the state of the current transaction to be invalidated,
the transaction is classified as an uncommittable transaction. An error that ordinarily ends a
transaction outside a TRY block causes a transaction to enter an uncommittable state when the
error occurs inside a TRY block. An uncommittable transaction can only perform read operations or a
ROLLBACK TRANSACTION. The transaction cannot execute any Transact-SQL statements that would
generate a write operation or a COMMIT TRANSACTION. The XACT_STATE function returns a value
of -1 if a transaction has been classified as an uncommittable transaction. When a batch finishes,
the Database Engine rolls back any active uncommittable transactions. If no error message was
sent when the transaction entered an uncommittable state, when the batch finishes, an error
message will be sent to the client application. This indicates that an uncommittable transaction was
detected and rolled back.
For more information about uncommittable transactions and the XACT_STATE function, see Using
TRY...CATCH in Transact-SQL and XACT_STATE (Transact-SQL).
Examples
A. Using TRY…CATCH
The following example shows a SELECT statement that will generate a divide-by-zero error. The
error causes execution to jump to the associated CATCH block.
Copy Code
USE AdventureWorks;
GO
BEGIN TRY
SELECT 1/0;
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;
GO
The following example shows how a TRY…CATCH block works inside a transaction. The statement
inside the TRY block generates a constraint violation error.
Copy Code
USE AdventureWorks;
GO
BEGIN TRANSACTION;
BEGIN TRY
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;
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
END CATCH;
IF @@TRANCOUNT > 0
COMMIT TRANSACTION;
GO
The following example shows how to use the TRY…CATCH construct to handle errors that occur
inside a transaction. The XACT_STATE function determines whether the transaction should be
committed or rolled back. In this example, SET XACT_ABORT is ON. This makes the transaction
uncommittable when the constraint violation error occurs.
Copy Code
USE AdventureWorks;
GO
GO
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
BEGIN TRY
BEGIN TRANSACTION;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
EXECUTE usp_GetErrorInfo;
-- Test XACT_STATE:
-- be rolled back.
IF (XACT_STATE()) = -1
BEGIN
END;
IF (XACT_STATE()) = 1
BEGIN
'Committing transaction.'
COMMIT TRANSACTION;
END;
END CATCH;
GO
GOTO (Transact-SQL)
Alters the flow of execution to a label. The Transact-SQL statement or statements that follow GOTO
are skipped and processing continues at the label. GOTO statements and labels can be used
anywhere within a procedure, batch, or statement block. GOTO statements can be nested.
Syntax
label :
GOTO label
Arguments
label
Is the point after which processing starts if a GOTO is targeted to that label. Labels must
follow the rules for identifiers. A label can be used as a commenting method whether GOTO is
used.
Remarks
GOTO can exist within conditional control-of-flow statements, statement blocks, or procedures, but
it cannot go to a label outside the batch. GOTO branching can go to a label defined before or after
GOTO.
Permissions
Examples
SET @Counter = 1;
BEGIN
SELECT @Counter
END
Branch_One:
Branch_Two:
Branch_Three: