CH 08
CH 08
Transactions and
Error Handling
Objectives
• Explore transaction fundamentals in SQL Server 2005.
• Understand the types of errors that can occur and how to handle them.
• Learn how to avoid blocking and deadlocks in transactions.
• Create explicit transactions.
• Create stored procedures using @@ERROR to handle errors.
• Use TRY/CATCH error handling.
• Create user-defined errors.
• Explore uncommittable transactions with XACT_STATE.
The files associated with this chapter are located in the following folder:
{Install Folder}\Transactions
This chapter’s lab does not include additional files.
Transaction Concepts
Simply put, a transaction is a collection of tasks that perform as a single unit of
work. In database terms, all parts of the transaction must all be saved to the
database, or all must be abandoned. Executing multiple tasks within the scope
of a single transaction enforces the connection between those tasks. If one task
fails, then all tasks are rolled back.
Transaction Types
SQL Server supports three types of transactions:
Autocommit is the default mode for working with transactions. A developer does not
need to issue any transactional statements⎯SQL Server takes care of everything
behind the scenes. If a statement completes successfully, it is committed; if it
encounters any error, it is rolled back. A connection to an instance of the Database
Engine operates in Autocommit mode whenever explicit or implicit transactions do not
override this default mode. Autocommit mode is also the default mode for ADO, OLE
DB, ODBC, and DB-Library.
SQL Server uses a dynamic locking strategy to determine the most cost-
effective locks by performing calculations that correct the granularity of
locking for your data operations. For instance, a SELECT statement that reads
all the rows in a table would expend more resources if it were to use shared
row locking rather than page-level or table-level locking. SQL Server will
perform the calculation behind the scenes and determine the most efficient
lock mode for any given operation. You can override this mechanism with lock
hints, but should avoid doing so without a good reason.
The Locking in the Database Engine topic in SQL Server Books Online
provides more detail about locking and monitoring locks.
BEGIN TRAN
UPDATE dbo.Categories
SET CategoryName = 'Drinks'
WHERE CategoryID = 1;
Open a second connection and execute this SELECT statement. Note that the
wheels are spinning but no results are returned. Issue a ROLLBACK in the
first connection. The SELECT statement then executes in the second
connection, displaying the data.
SQL Server chooses as a deadlock victim the transaction that is least expensive
to roll back, generating error code 1205. You can test for this error and either
resubmit or cancel the query. You can also specify the priority of a session by
using the SET DEADLOCK_PRIORITY and choosing LOW, NORMAL, or
HIGH or by setting the priority to an integer value in the range of (-10 to 10).
The following statement sets it to LOW, making the session more likely to be a
deadlock victim.
See Tranasctions In the following example, none of the INSERT statements are executed
ErrorHandling.SQL because of the compile error in the third INSERT statement. It appears that the
first two INSERT statements are rolled back, but in fact they never executed.
The Messages pane displays the error and that no rows were returned by the
SELECT statement, as shown in Figure 3.
Figure 3. No rows are inserted from a batch if there is a compile error in one
statement.
Figure 4. Rows without syntax errors are inserted if they are not part of a batch
with a syntax error.
Runtime Errors
If you have a runtime error in one statement in a batch, other statements that
do not have errors will commit. This example batch adds new rows and
generates a duplicate primary key error on the third row. Figure 5 shows that
two rows from the batch were inserted.
Figure 5. The results from inserting rows with a runtime error in one statement.
Figure 6. Rows around an invalid table name in a batch are also inserted.
Nested Transactions
Transactions can be nested inside other transactions. This usually occurs when
a stored procedure, with its own transactions, executes another stored
procedure with its own transactions. SQL Server always gives precedence to
the outer transaction. However, if an error occurs at any level, all of the nested
transactions are rolled back. This behavior exists to protect your data. If any
part of a transaction was able to commit while another part was rolled back,
your data would be left in an inconsistent state.
Using @@TRANCOUNT
The @@TRANCOUNT function returns the number of active transactions for
the current connection. In this simple example, @@TRANCOUNT determines
whether a transaction is active and if so, rolls it back. The Messages pane
shown in Figure 7 seems misleading until you consider that the rows affected
message occurs before the PRINT statements. This indicates that no error
occurred that would cause the transaction to be rolled back. Executing a
SELECT statement on the Employees table shows the original value of
Davolio.
BEGIN TRANSACTION;
UPDATE dbo.Employees
SET LastName = 'Da Volio'
WHERE LastName = 'Davolio';
IF @@TRANCOUNT > 0
BEGIN
PRINT N'The transaction is active.';
ROLLBACK TRANSACTION;
PRINT N'The transaction has been rolled back.';
END
GO
When it comes to error handling, the best offense is a good defense. Whatever
you can do to prevent runtime errors will simplify the process of handling
them when they do occur. Transactions and error handling are most effective
within stored procedures. So, to make your transactions more efficient and
secure, set default values for all input parameters, validate them, and exit the
stored procedure if the parameters don’t pass muster.
The following example creates a transaction with error handling. In this case,
the code attempts to insert a new record in the Customers table, which violates
the primary key constraint on CustomerID. The error value is captured in a
local variable, and a message is displayed.
EXECUTE dbo.InsertCategoryTransaction
@CategoryName,
@CategoryID OUTPUT,
@ReturnCode OUTPUT,
@ReturnMessage OUTPUT;
If you test with an existing value as shown in the previous code, you should
see the results shown in Figure 8.
Using RAISERROR
RAISERROR is useful when you want to return an error message or
informational message to the caller. The syntax allows you to raise the error
with a message ID, string, or local variable. The severity and state arguments
are required; however, the state argument is an arbitrary value.
How RAISERROR behaves depends on the severity level of the error. Severity
levels of 1 to 10 are considered informational messages, not errors. Severity
levels of 11 to 19 are considered errors. Severity levels from 19 to 25 indicate
fatal errors and are required to be logged. For complete information about error
severity levels, see Database Engine Error Severities in SQL Server Books
Online.
sp_addmessage
[@msgnum=] msg_id,
[@severity=] severity,
[@msgtext=] 'msg'
[,[ @lang = ] 'language']
[,[ @with_log=] 'with_log']
[,[ @replace=] 'replace']
The following example creates a custom error message with a severity level of
15 that returns an error message with the session ID and name of the database
in which the error occurred as substitution string placeholders. To raise the
error, you need to use @@SPID and DB_NAME() to retrieve those values:
TRY/CATCH Overview
TRY/CATCH error handling works much the same way in SQL Server as it
does in a programming language. You place a statement in a BEGIN TRY
block followed by a BEGIN CATCH block. If the statement generates an
error, the statements in the BEGIN CATCH block execute until the END
CATCH is reached. The END TRY is skipped. If no error occurs, the CATCH
block is skipped entirely and processing continues to the END TRY block.
BEGIN TRY
BEGIN TRANSACTION;
INSERT INTO dbo.Customers (CustomerID, CompanyName)
VALUES ('ALFKI', 'New ALFKI');
COMMIT
PRINT 'Transaction committed.';
END TRY
BEGIN CATCH
ROLLBACK
PRINT 'Transaction rolled back.';
END CATCH
Error Chaining
If you generate an error that is not within a TRY/CATCH block, that error is
passed back up the call stack to the caller. For example, Sproc1 calls Sproc2
from inside a TRY/CATCH block. Sproc2 does not have a TRY/CATCH
block and generates an error. That error is passed back up the stack to Sproc1
and is processed in Sproc1’s CATCH block. If there is no TRY/CATCH block,
the error is passed to the client.
All errors are handled in the CATCH block and are not returned to the caller.
When the code in the CATCH block completes, control passes to the statement
immediately after the END CATCH statement. If you need to pass error
information to the client, you can do so using other means.
This is what the previous example looks like when it is expanded to include all
of the error functions in the CATCH block. The results are shown in Figure 12.
BEGIN TRY
BEGIN TRANSACTION;
INSERT INTO dbo.Customers (CustomerID, CompanyName)
VALUES ('ALFKI', 'New ALFKI');
COMMIT
PRINT 'Transaction committed.';
END TRY
BEGIN CATCH
ROLLBACK
SELECT
ERROR_NUMBER() AS Number,
ERROR_SEVERITY() AS Severity,
ERROR_STATE() AS State,
ERROR_PROCEDURE() AS procedureName,
ERROR_LINE() AS Line,
ERROR_MESSAGE() AS messageText;
PRINT 'Transaction rolled back.';
END CATCH
Figure 12. Using the new error handling functions to display information.
BEGIN TRY
RAISERROR ('Error severity 9', 9, 1);
RAISERROR ('Error severity 16', 16, 1);
END TRY
BEGIN CATCH
DECLARE @ErrorMessage NVARCHAR(4000);
DECLARE @ErrorSeverity INT;
SELECT
@ErrorMessage = ('In CATCH block: ' +
ERROR_MESSAGE()),
@ErrorSeverity = ERROR_SEVERITY();
RAISERROR (@ErrorMessage,
@ErrorSeverity, 1);
END CATCH;
GO
BEGIN TRY
-- Test if CategoryName null.
IF @CategoryName IS NULL
RAISERROR ('Validation failed: Null CategoryName.',
16, 1);
BEGIN CATCH
-- Test to see if we're in a transaction.
IF @@TRANCOUNT > 0
ROLLBACK
EXECUTE dbo.InsertCategoryTryCatch
@CategoryName,
@CategoryID OUTPUT,
@ReturnCode OUTPUT,
@ReturnMessage OUTPUT;
You are most likely to encounter this state when the severity level is 17 or
higher, but you can simulate it by setting SET XACT_ABORT ON, which you
can use to force any transaction to enter an uncommittable state.
BEGIN CATCH
IF (XACT_STATE()) = -1
BEGIN
DECLARE @category nvarchar(50)
SELECT @Category =
(SELECT CategoryName FROM dbo.Categories WHERE
CategoryID=1)
ROLLBACK
PRINT 'The ' + @category + ' category cannot be
deleted.';
END
END CATCH
GO
Summary
• A transaction is a series of SQL statements that must either all succeed
or all fail.
• SQL Server runs all implicit transactions in Autocommit mode by
default.
• You can implement explicit transactions with BEGIN
TRANSACTION, COMMIT, and ROLLBACK statements.
• Use transactions, isolation levels, and lock hints wisely to avoid
blocking and deadlocks.
• Syntax errors cause all statements in a batch to be rolled back in
Autocommit transactions.
• Runtime errors and invalid object name errors in a batch do not
prevent other statements in the batch from being committed.
• @@TRANCOUNT returns the number of active transactions.
• Output parameters in a stored procedure provide a good way to return
error information.
• Use TRY/CATCH error handling with errors that have a severity level
greater than 10 and that do not terminate the connection.
• SQL Server 2005 introduces a rich set of error handling functions to
return information from inside a CATCH block.
• Use RAISERROR to return a user-defined error.
• Use XACT_STATE to handle uncommittable transactions.
Questions
1. What are the four parts of the ACID test that SQL Server transactions
ensure?
5. Which function can you use in a CATCH block to return the name of the
stored procedure or function in which the error occurred?
Answers
1. What are the four parts of the ACID test that SQL Server transactions
ensure?
Atomicity, consistency, isolation, and durability.
5. Which function can you use in a CATCH block to return the name of the
stored procedure or function in which the error occurred?
ERROR_PROCEDURE()
Lab 8:
Transactions and
Error Handling
Lab 8 Overview
In this lab you’ll learn to create explicit transactions and to implement
TRY/CATCH error handling.
Objective
The objective of this section is to create an explicit transaction in a stored
procedure to add a new customer to the dbo.Customers table in Northwind.
You’ll create input parameters for the CustomerID and CompanyName values
and an output parameter to send back message information. You’ll create a
variable to test whether an error occurred. You’ll commit the transaction if
there are no errors, and roll it back if there are. You’ll execute the stored
procedure by supplying the values ‘ALFKI’ and Al’s Ale House and display
the message contained in the output parameter.
Things to Consider
• How do you create a stored procedure?
• How do you declare input and output parameters?
• How do you implement an explicit transaction?
• How do you test for errors?
• How do you commit/roll back the transaction?
• How do you execute the stored procedure?
Step-by-Step Instructions
1. Open a new query window and connect to the Northwind database.
USE Northwind;
GO
SET NOCOUNT ON
DECLARE @err int;
4. Begin the transaction and test whether there are any errors.
BEGIN TRANSACTION
INSERT INTO dbo.Customers (CustomerID, CompanyName)
VALUES (@CustomerID, @CompanyName);
SELECT @err = @@ERROR
IF @err = 0
BEGIN
COMMIT
SET @ReturnMessage='Transaction succeeded.';
END
ELSE
BEGIN
ROLLBACK
SET @ReturnMessage='Transaction failed.';
END
ELSE
BEGIN
ROLLBACK
SET @ReturnMessage='Transaction failed.';
END
EXECUTE dbo.AddCustomerLab1
@CompanyID,
@CompanyName,
@ReturnMessage OUTPUT;
Objective
In this exercise, you’ll add TRY/CATCH error handling to the stored
procedure you created in the first exercise. You’ll use the
ERROR_MESSAGE() function to return an error message in the output
parameter.
Things to Consider
• How do you implement TRY/CATCH error handling?
• How do you commit/roll back the transaction?
• How do you return a message in an output parameter from a built-in
function?
Step-by-Step Instructions
1. Create a new procedure named AddCustomerLab2 and declare the same
parameters you used in the first exercise.
2. Create the BEGIN TRY block with the INSERT statement and commit the
transaction.
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO dbo.Customers (CustomerID, CompanyName)
VALUES (@CustomerID, @CompanyName);
COMMIT
SET @ReturnMessage='Transaction succeeded.';
END TRY
3. Create the CATCH block to roll back the transaction. Set the output
parameter to the results of the ERROR_MESSAGE() function.
BEGIN CATCH
ROLLBACK
SET @ReturnMessage = ERROR_MESSAGE();
END CATCH
4. To execute the stored procedure, use the same commands that you did in
the first exercise. Change the name of the stored procedure, as shown in
bold. The output parameter message will be displayed in the results pane.