{"id":1019,"date":"2010-10-28T00:00:00","date_gmt":"2010-10-28T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/defensive-error-handling\/"},"modified":"2021-09-29T16:21:56","modified_gmt":"2021-09-29T16:21:56","slug":"defensive-error-handling","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/defensive-error-handling\/","title":{"rendered":"Defensive Error Handling"},"content":{"rendered":"<div id=\"pretty\">\n<p class=\"start\">The ability to handle errors is essential in any programming language and, naturally, we have to implement safe error handling in our T-SQL if we want to build solid SQL Server code. SQL Server 2005, and later, superseded the old style <span class=\"STCodeinTextChar\">@@Error<\/span> error handling, with the <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> blocks that are more familiar to Java and C# programmers.<\/p>\n<p>While use of <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> certainly is the best way to handle errors in T-SQL, it is not without difficulties. Error handling in T-SQL can be very complex, and its behavior can sometimes seem erratic and inconsistent. Furthermore, error handling in Transact SQL lacks many features that developers who use languages such as Java and C# take for granted. For example, in SQL Server 2005 and 2008, we cannot even re-throw an error without changing its error code. This complicates handling errors, because we have to write separate conditions for detecting exceptions caught for the first time, and for detecting re-thrown exceptions.<\/p>\n<p>This article will demonstrate simple SQL Server error handling, using <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> and transactions, describe the most common problems with <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> error handling, and advocate that the defensive programmer, where possible, implements only simple error checking and handling in SQL Server, with client-side error handling used to enforce what is done on the server.<\/p>\n<h1>Prepare for Unanticipated Failure<\/h1>\n<p>Any statement can, and at some point inevitably will, fail. This may seem to be a statement of the obvious, but too many programmers seem to assume that once their code &#8220;works&#8221; then the data modifications and queries that it contains will always succeed.<\/p>\n<p>In fact, data modifications can and do fail unexpectedly. For example, the data may not validate against a constraint or a trigger, or the command may become a deadlock victim. Even if the table does not have any constraints or triggers at the time the code is developed, they may be added later. It is wise to assume that our modifications will not always succeed.<\/p>\n<p>Many queries, too, can fail. Just as a modification can become a deadlock victim, so can a <span class=\"STCodeinTextChar\">SELECT<\/span> (unless that <span class=\"STCodeinTextChar\">SELECT<\/span> is running under either of the two snapshot isolation levels). If a <span class=\"STCodeinTextChar\">SELECT<\/span> statement utilizes a user-defined function, then errors may occur in that function that will cause the query to fail. Other common causes of failure are queries that attempt to use a temporary table that does not exist, or contain subqueries that return more than one value.<\/p>\n<p>Listing 1-1 demonstrates a very simple case of a <span class=\"STCodeinTextChar\">SELECT<\/span> statement that may succeed or fail depending on locale settings.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">CREATE VIEW dbo.NextNewYearEve AS \nSELECT DATEADD\n&#160;&#160;&#160; &#160;&#160;&#160;&#160;(YEAR,\n&#160;&#160;&#160; &#160;&#160;&#160;&#160;&#160;DATEDIFF(year, '12\/31\/2000', CURRENT_TIMESTAMP),\n&#160;&#160;&#160;&#160; &#160;&#160;&#160;&#160;'12\/31\/2000'\n&#160;&#160;&#160; &#160;&#160;&#160;&#160;) AS NextNewYearEve ; \nGO \n\nSET LANGUAGE us_english ; \nSELECT&#160; NextNewYearEve\nFROM&#160;&#160;&#160; dbo.NextNewYearEve ; \n\nChanged language setting to us_english.\nNextNewYearEve\n-----------------------\n2009-12-31 00:00:00.000\n\nSET LANGUAGE Norwegian ; \nSELECT&#160; NextNewYearEve\nFROM&#160;&#160;&#160; dbo.NextNewYearEve ; \nGO \n\nChanged language setting to Norsk.\nNextNewYearEve\n-----------------------\nMsg 241, Level 16, State 1, Line 2\nConversion failed when converting date and\/or time from character string.\n\nDROP VIEW dbo.NextNewYearEve ;&#160; \n<\/pre>\n<p class=\"caption\">Listing 1-1: Language settings can cause certain date queries to fail<\/p>\n<p>The main point is clear: when we develop T-SQL code, we cannot assume that our queries or data modifications will always succeed and we need to be prepared for such failures and handle them gracefully. When an unexpected error occurs during data modification, it is essential that execution of the statement is terminated, the database is returned to the state it was in before the statement started, and a message is sent to the calling client, giving some details of the error that occurred and the action taken as a result. Likewise, if a <span class=\"STCodeinTextChar\">SELECT<\/span> fails that is part of a longer transaction that has already modified data then these modifications must be undone as well.<\/p>\n<h1>Using Transactions for Data Modifications<\/h1>\n<p>In many cases, during data modifications, we cannot take our database from one consistent state to another in a single statement. If a data modification requires more than one statement to effect the required change, then explicit transactions should be used to ensure that these statements succeed or fail as a unit, and that our error handling can handle them as a unit.<\/p>\n<p>For example, suppose that we need to log in one table all the modifications made to another table. Listing 1-2 shows the code to create the table to be modified (<span class=\"STCodeinTextChar\">Codes<\/span>) and the table in which the modifications will be logged (<span class=\"STCodeinTextChar\">CodeDescriptionsChangeLog<\/span>).<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">IF EXISTS ( SELECT&#160; *\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160;&#160; INFORMATION_SCHEMA.TABLES\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE&#160;&#160; TABLE_NAME = 'Codes'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;AND TABLE_SCHEMA = 'dbo' )\n&#160; BEGIN;\n&#160;&#160;&#160; -- we used a Codes table in a previous chapter\n&#160;&#160;&#160; -- let us make sure that is does not exist any more\n&#160;&#160;&#160; DROP TABLE dbo.Codes ;\n&#160; END ;\nGO\nCREATE TABLE dbo.Codes\n&#160; (\n&#160;&#160;&#160; Code VARCHAR(10) NOT NULL ,\n&#160;&#160;&#160; Description VARCHAR(40) NULL ,\n&#160;&#160;&#160; CONSTRAINT PK_Codes PRIMARY KEY CLUSTERED ( Code )\n&#160; ) ; \nGO\n\n-- we did not use this table name before in this book,\n-- so there is no need to check if it already exists\nCREATE TABLE dbo.CodeDescriptionsChangeLog\n&#160;&#160;&#160; (\n&#160;&#160;&#160;&#160;&#160; Code VARCHAR(10) NOT NULL ,\n&#160;&#160;&#160;&#160;&#160; ChangeDate DATETIME NOT NULL ,\n&#160;&#160;&#160;&#160;&#160; OldDescription VARCHAR(40) NULL ,\n&#160;&#160;&#160;&#160;&#160; NewDescription VARCHAR(40) NULL ,\n&#160;&#160;&#160;&#160;&#160; CONSTRAINT PK_CodeDescriptionsChangeLog PRIMARY KEY ( Code, ChangeDate )\n&#160;&#160;&#160; ) ;\n<\/pre>\n<p class=\"caption\">Listing 1-2: The <span class=\"STCodeinTextChar\">Codes and <\/span><span class=\"STCodeinTextChar\">CodeDescriptionsChangeLog tables<\/span><\/p>\n<p>Note that the log table does not have a <span class=\"STCodeinTextChar\">FOREIGN<\/span> <span class=\"STCodeinTextChar\">KEY<\/span> constraint referring to the <span class=\"STCodeinTextChar\">Codes<\/span> table, because the log records need to be kept even if we delete the corresponding rows in <span class=\"STCodeinTextChar\">Codes<\/span>.<\/p>\n<p>The procedure shown in Listing 1-3 modifies the <span class=\"STCodeinTextChar\">Codes<\/span> table, and logs the change in the <span class=\"STCodeinTextChar\">CodeDescriptionsChangeLog table.<\/span><\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">CREATE PROCEDURE dbo.ChangeCodeDescription\n&#160;&#160;&#160; @Code VARCHAR(10) ,\n&#160;&#160;&#160; @Description VARCHAR(40)\nAS \n&#160;&#160;&#160; BEGIN ; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; INSERT&#160; INTO dbo.CodeDescriptionsChangeLog\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ( Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ChangeDate ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; OldDescription ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; NewDescription\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; )\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT&#160; Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; CURRENT_TIMESTAMP ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Description ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @Description\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160;&#160; dbo.Codes\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE&#160;&#160; Code = @Code ; \n\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; UPDATE&#160; dbo.Codes\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; SET&#160;&#160;&#160;&#160; Description = @Description\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE&#160;&#160; Code = @Code ; \n&#160;&#160;&#160; END ;&#160; \n<\/pre>\n<p class=\"caption\">Listing 1-3: The <span class=\"STCodeinTextChar\">ChangeCodeDescription<\/span> stored procedure<\/p>\n<p>Listing 1-4 runs a simple smoke test on the new procedure.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">INSERT&#160; INTO dbo.Codes\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; ( Code, Description )\nVALUES&#160; ( 'IL', 'Ill.' ) ; \nGO \n\nEXEC dbo.ChangeCodeDescription\n&#160; @Code = 'IL', \n&#160; @Description = 'Illinois' ; \nGO \n\nSELECT&#160; Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; OldDescription + ', ' + NewDescription\nFROM&#160;&#160;&#160; dbo.CodeDescriptionsChangeLog ;&#160; \n\nCode&#160;&#160;&#160;&#160;&#160;&#160; \n---------- ---------------------------------------------------------\nIL&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Ill., Illinois\n<\/pre>\n<p class=\"caption\">Listing 1-4: A smoke test on the <span class=\"STCodeinTextChar\">ChangeCodeDescription<\/span> stored procedure<\/p>\n<p>It looks like the procedure works, right? Note, however, that this stored procedure does not attempt to determine whether or not either of the two modifications failed, and it does not handle possible errors. Of course, one might argue that this stored procedure, could be a component of a perfectly valid system, if it is invoked by an application that does all the error handling. However, that does not make it, as is, a valid component. There&#8217;s a huge risk that a developer who builds another application may find this procedure and decide to call it, unaware of the required error handling in the calling procedure.<\/p>\n<p>It may seem that nothing could possibly go wrong during these two trivial modifications, but we still cannot assume that both modifications will always succeed. In fact, even in this trivial example, we can devise a case where one modification can fail: if two modifications occur simultaneously, we may get a primary key violation on the <span class=\"STCodeinTextChar\">CodeDescriptionsChangeLog<\/span> table.<\/p>\n<p>Rather than reproduce that case here, we can prove the same point simply by creating a <span class=\"STCodeinTextChar\">CHECK<\/span> constraint that prohibits inserts and updates against the <span class=\"STCodeinTextChar\">CodeDescriptionsChangeLog<\/span> table, and demonstrates what happens when one of our modifications fails and we do nothing to detect and handle it.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SET XACT_ABORT OFF ;\n-- if&#160; XACT_ABORT OFF were set to ON ,\n-- the code below would behave differently.\n-- We shall discuss it later in this chapter.\n\nDELETE&#160;&#160; FROM dbo.CodeDescriptionsChangeLog ;\n\nBEGIN TRANSACTION ;\nGO\n\n-- This constraint temporarily prevents all inserts\n-- and updates against the log table.\n-- When the transaction is rolled back, the constraint\n-- will be gone.\nALTER TABLE dbo.CodeDescriptionsChangeLog \nADD CONSTRAINT CodeDescriptionsChangeLog_Immutable \n&#160;&#160; CHECK(1&lt;0) ;\nGO\n\nEXEC dbo.ChangeCodeDescription\n&#160; @Code = 'IL',\n&#160;&#160;@Description = 'other value' ; \nGO\n\n-- dbo.Codes table has been updated\nSELECT&#160;&#160; Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Description\nFROM&#160;&#160;&#160;&#160; dbo.Codes ; \n\n-- dbo.CodeDescriptionsChangeLog has not been updated\nSELECT&#160;&#160; Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; OldDescription + ', ' + NewDescription\nFROM&#160;&#160;&#160;&#160; dbo.CodeDescriptionsChangeLog ; \nGO\n\nROLLBACK ;\n\nMsg 547, Level 16, State 0, Procedure ChangeCodeDescription, Line 6\nThe INSERT statement conflicted with the CHECK constraint \"CodeDescriptionsChangeLog_Immutable\". The conflict occurred in database \"Test\", table \"dbo.CodeDescriptionsChangeLog\".\nThe statement has been terminated.\n\n(1 row(s) affected)\nCode&#160;&#160;&#160;&#160;&#160;&#160; Description\n---------- ----------------------------------------\nIL&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; other value\n\n(1 row(s) affected)\n\nCode&#160;&#160;&#160;&#160;&#160;&#160; \n---------- ----------------------------------------------------------\n\n(0 row(s) affected)\n<\/pre>\n<p class=\"caption\">Listing 1-5: An <span class=\"STCodeinTextChar\">INSERT<\/span> into <span class=\"STCodeinTextChar\">CodeDescriptionsChangeLog<\/span> fails, but the <span class=\"STCodeinTextChar\">UPDATE<\/span> of <span class=\"STCodeinTextChar\">Codes<\/span> succeeds, and we end up with an <span class=\"STCodeinTextChar\">UPDATE<\/span> that has not been logged<\/p>\n<p>In order to avoid this situation, we need to begin a transaction, attempt to do both modifications, determine whether or not both completed successfully, and commit the transaction only if both modifications succeeded. If either modification failed, we need to rollback the transaction, as part of our error handling. T-SQL allows several ways to accomplish that. Let&#8217;s begin with the simplest approach: using transactions along with the <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> setting.<\/p>\n<h1>Using Transactions and XACT_ABORT to Handle Errors<\/h1>\n<p>In many cases, we do not need sophisticated error handling. Quite frequently all we need to do, in case of an error, is roll back all the changes and throw an exception, so that the client knows that there is a problem and will handle it. In such situations, a perfectly reasonable approach is to make use of the <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> setting.<\/p>\n<p>By default, in SQL Server this setting is <span class=\"STCodeinTextChar\">OFF<\/span>, which means that in some circumstances SQL Server can continue processing when a T-SQL statement causes a run-time error. In other words, for less severe errors, it may be possible to rollback only the statement that caused the error, and to continue processing other statements in the transaction.<\/p>\n<p>If <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> is turned on, SQL Server stops processing as soon as a T-SQL run-time error occurs, and the entire transaction is rolled back. When handling unexpected, unanticipated errors, there is often little choice but to cease execution and rollback to a point where there system is in a &#8216;known state&#8217;. Otherwise you risk seeing partially completed transactions persisted to your database, and so compromising data integrity. In dealing with such cases, it makes sense to have <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> turned <span class=\"STCodeinTextChar\">ON<\/span>.<\/p>\n<div class=\"note\">\n<p class=\"note\"><b>Data Modifications via OLDE DB<\/b><br \/>Note that in some cases <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> is already set to <span class=\"STCodeinTextChar\">ON<\/span> by default. For example, OLE DB will do that for you. However, it is usually preferable to explicitly set it, because we do not know in which context our code will be used later.<\/p>\n<\/div>\n<p>Listing 1-6 illustrates a basic error handling approach, whereby our modifications take place within an explicit transaction, having set <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> to <span class=\"STCodeinTextChar\">ON<\/span>. The <span class=\"STCodeinTextChar\">PRINT<\/span> commands in the procedure are for demonstration purposes only; we would not need them in production code. <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">ALTER PROCEDURE dbo.ChangeCodeDescription\n&#160;&#160;&#160; @Code VARCHAR(10) ,\n&#160;&#160;&#160; @Description VARCHAR(40)\nAS \n&#160;&#160;&#160; BEGIN ; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; SET XACT_ABORT ON ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; BEGIN TRANSACTION ; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; INSERT&#160; INTO dbo.CodeDescriptionsChangeLog\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ( Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ChangeDate ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; OldDescription ,\n&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;NewDescription\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; )\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT&#160; Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; current_timestamp ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Description ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @Description\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160;&#160; dbo.Codes\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE&#160;&#160; Code = @Code ; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; PRINT 'First modifications succeeded' ;\n\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; UPDATE&#160; dbo.Codes\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; SET&#160;&#160;&#160;&#160; Description = @Description\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE&#160;&#160; Code = @Code ; \n&#160; -- the following commands execute only if both\n&#160; -- modifications succeeded\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; PRINT 'Both modifications succeeded, committing\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;the transaction' ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; COMMIT ;\n&#160;&#160;&#160; END ;\n<\/pre>\n<p class=\"caption\">Listing 1-6: Using the <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> setting and an explicit transaction<\/p>\n<p>Note that although we want to roll back all the changes if an error occurs, we do not need to explicitly determine if there are any errors, and we do not need to explicitly invoke <span class=\"STCodeinTextChar\">ROLLBACK<\/span> in our code; when <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> is set to <span class=\"STCodeinTextChar\">ON<\/span>, it all happens automatically. Listing 1-7 tests our altered stored procedure.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SET NOCOUNT ON ;\nSET XACT_ABORT OFF ;\n\nDELETE&#160;&#160; FROM dbo.CodeDescriptionsChangeLog ;\n\nBEGIN TRANSACTION ;\nGO\n\n-- This constraint temporarily prevents all inserts\n-- and updates against the log table.\n-- When the transaction is rolled back, the constraint\n-- will be gone.\nALTER TABLE dbo.CodeDescriptionsChangeLog \nADD CONSTRAINT CodeDescriptionsChangeLog_Immutable \n&#160;&#160; CHECK(1&lt;0) ;\nGO\n\nEXEC dbo.ChangeCodeDescription\n&#160; @Code = 'IL',\n&#160; @Description = 'other value' ; \nGO\n-- transaction is rolled back automatically\nSELECT @@TRANCOUNT AS [@@TRANCOUNT after stored procedure call] ;\n\n-- dbo.Codes table has not been updated\nSELECT&#160;&#160; Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Description\nFROM&#160;&#160;&#160;&#160; dbo.Codes ; \n\n-- dbo.CodeDescriptionsChangeLog has not been updated\nSELECT&#160;&#160; Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; OldDescription + ', ' + NewDescription\nFROM&#160;&#160;&#160;&#160; dbo.CodeDescriptionsChangeLog ; \n\nMsg 547, Level 16, State 0, Procedure ChangeCodeDescription, Line 8\nThe INSERT statement conflicted with the CHECK constraint \"CodeDescriptionsChangeLog_Immutable\". The conflict occurred in database \"test\", table \"dbo.CodeDescriptionsChangeLog\".\n@@TRANCOUNT after stored procedure call\n---------------------------------------\n0\n\nCode&#160;&#160;&#160;&#160;&#160;&#160; Description\n---------- ----------------------------------------\nIL&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Illinois\n\nCode&#160;&#160;&#160;&#160;&#160;&#160; \n---------- -----------------------------------------\n<\/pre>\n<p class=\"caption\">Listing 1-7: Testing the altered stored procedure<\/p>\n<p>As we have seen, the stored procedure worked perfectly well. Of course, this is just the first in as series of tests we should perform on our stored procedure. Complete testing would include:<\/p>\n<ul>\n<li>Making sure that if both the modification of the <span class=\"STCodeinTextChar\">Codes<\/span> table and the <span class=\"STCodeinTextChar\">INSERT<\/span> into the <span class=\"STCodeinTextChar\">CodeDescriptionsChangeLog<\/span> table succeed, then the transaction commits and both changes persist.  <\/li>\n<li>Verifying that if an <span class=\"STCodeinTextChar\">UPDATE<\/span> of the <span class=\"STCodeinTextChar\">Codes<\/span> table fails then the transaction rolls back. To reproduce a failure, we can use a similar technique; a <span class=\"STCodeinTextChar\">CHECK<\/span> constraint that makes sure all <span class=\"STCodeinTextChar\">UPDATE<\/span>s against <span class=\"STCodeinTextChar\">Codes<\/span> table fail.  <\/li>\n<li>Invoking the stored procedure without an outstanding transaction, when <span class=\"STCodeinTextChar\">@@TRANCOUNT<\/span> is 0. In that case we shall have to explicitly drop the <span class=\"STCodeinTextChar\">CHECK<\/span> constraint which we create in our test.<\/li>\n<\/ul>\n<p>I encourage you to tweak Listing 1-7 and try out these other tests. In many cases, this simple approach of setting <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> to <span class=\"STCodeinTextChar\">ON<\/span> and using an explicit transaction for modifications gets the job done without much effort. We should use this simple and robust approach unless we really need more sophisticated functionality from our error handling.<\/p>\n<p>If we really want to do some more complex error handling on the server, using T-SQL, then we should use <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> blocks, which are available in SQL Server 2005 and upwards.<\/p>\n<h1>Using TRY&#8230;CATCH blocks to Handle Errors<\/h1>\n<p>To handle errors in T-SQL modules, in SQL Server 2005 and upwards, we can use <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> blocks. If any command inside the <span class=\"STCodeinTextChar\">TRY<\/span> block raises an error, the execution of the <span class=\"STCodeinTextChar\">TRY<\/span> block terminates immediately, which is similar to the behavior under <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> setting. But, unlike with <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> where the whole batch terminates, only the execution of the code inside the <span class=\"STCodeinTextChar\">TRY<\/span> block terminates, and the <span class=\"STCodeinTextChar\">CATCH<\/span> block begins to execute. <\/p>\n<p>In cases where you are aware that a certain specific error could occur, then your error handling strategy can be different. You may attempt to add code to your <span class=\"STCodeinTextChar\">CATCH<\/span> block that corrects the error, or at least allows processing to continue. In these cases, it makes more sense to have <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> set to <span class=\"STCodeinTextChar\">OFF<\/span>, so that you can handle the errors, and inform the calling client of what happened, without rolling back the entire batch.<\/p>\n<p>As will become clear as we progress, my current philosophy is that all but the simplest error handling should be implemented, ideally, in a client-side language where the error handling is more robust and feature-rich than it is in SQL Server <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span>.<\/p>\n<p>Therefore my goal here is not to cover <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> in full detail, but to set out, with examples, some of the reasons why error handling in T-SQL can be complex and a little bewildering. I really want to encourage you to either fully understand all the ins and outs of T-SQL error handling, or to not to use it at all, except in the simplest cases.<\/p>\n<p>Erland Sommarskog&#8217;s website, <a href=\"http:\/\/www.sommarskog.se\/\">http:\/\/www.sommarskog.se\/<\/a>, is an excellent source of information on error handling. The book &#8220;Expert SQL Server 2005 Development&#8221; by Adam Machanic, Hugo Kornelis, and Lara Rubbelke is another great resource.<\/p>\n<p>Finally, note that I do not cover &#8220;old-style&#8221; error handling, using <span class=\"STCodeinTextChar\">@@ERROR<\/span>, at all in this chapter. Use of <span class=\"STCodeinTextChar\">@@ERROR<\/span> has some well-known problems, such as inability to handle errors raised by triggers, and the fact that sometimes SQL Server simply fails to set its value correctly. In general, my advice would be to upgrade from <span class=\"STCodeinTextChar\">@@ERROR<\/span> to <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span>, or even better to client-side error handling for all but the simplest cases, as soon as possible.<\/p>\n<h2>A TRY&#8230;CATCH Example: Retrying After Deadlocks<\/h2>\n<p>Sometimes, it may make sense to use <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> blocks to retry the execution of a statement, after a deadlock. One must exercise caution when doing so as retrying an <span class=\"STCodeinTextChar\">UPDATE<\/span> statement in this manner may lead to lost updates, as we discuss in detail in Chapter 10 of my book, <i>Surviving Concurrent Modifications<\/i>. The defensive programmer must take all possible measures to ensure that the possibility of deadlocks is minimized but, in some cases, it may be deemed acceptable, in the short term at least, to automatically retry after a deadlock.<\/p>\n<p>In order to provide an example that you can run on your server, we&#8217;ll alter our <span class=\"STCodeinTextChar\">ChangeCodeDescription <\/span>stored procedure, as shown in Listing 1-8, so that it is high likely to be chosen as a deadlock victim, if it embraces in a deadlock with a competing session. Our goal here is not to demonstrate how to develop stored procedures that are unlikely to embrace in deadlocks, but to see how to use a <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> block to retry after a deadlock.<\/p>\n<p>If processing switches to our <span class=\"STCodeinTextChar\">CATCH<\/span> block, we will attempt to re-execute our transaction once more, in response to a deadlock; otherwise we will simply re-throw the error so that the calling client is notified and can respond.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">ALTER PROCEDURE dbo.ChangeCodeDescription\n&#160; @Code VARCHAR(10) ,\n&#160;&#160;@Description VARCHAR(40)\nAS \n&#160;&#160;BEGIN ;\n&#160;&#160;&#160; DECLARE @tryCount INT ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @OldDescription VARCHAR(40) ;\n&#160;&#160;&#160; SET DEADLOCK_PRIORITY LOW ;\n&#160;&#160;&#160; SET XACT_ABORT OFF ;\n&#160;&#160;&#160; SET @tryCount = 1 ;\n&#160;&#160;&#160; WHILE @tryCount &lt; 3 \n&#160;&#160;&#160;&#160;&#160; BEGIN \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; BEGIN TRY \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; BEGIN TRANSACTION ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SET @OldDescription = ( SELECT&#160; Description\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160;&#160; dbo.Codes\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE&#160;&#160; Code = @Code\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ) ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; UPDATE&#160; dbo.Codes\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SET&#160;&#160;&#160;&#160; Description = @Description\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE&#160;&#160; Code = @Code ; \n\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; INSERT&#160; INTO dbo.CodeDescriptionsChangeLog\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ( Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ChangeDate ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; OldDescription ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;NewDescription&#160; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; )\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT&#160; @Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; CURRENT_TIMESTAMP ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @OldDescription ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @Description ; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; PRINT 'Modifications succeeded' ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; COMMIT ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; RETURN 0 ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; END TRY\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; BEGIN CATCH\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; -- transaction is not rolled back automatically\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; -- we need to roll back explicitly\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;IF @@TRANCOUNT &lt;&gt; 0 \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;BEGIN ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;PRINT 'Rolling back' ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ROLLBACK ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; END ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; IF ERROR_NUMBER() &lt;&gt; 1205 \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; BEGIN \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; -- if this is not a deadlock, \"rethrow\" the error\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;DECLARE @ErrorMessage NVARCHAR(4000) ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SET @ErrorMessage = ERROR_MESSAGE() ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; RAISERROR('Error %s occurred in\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160;SelectCodeChangeLogAndCode'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ,16,1,@ErrorMessage) ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; RETURN -1 ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; END ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ELSE \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;BEGIN ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;PRINT 'Encountered a deadlock'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; END ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; END CATCH ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; SET @tryCount = @tryCount + 1 ;\n&#160;&#160;&#160;&#160;&#160; END ; \n&#160;&#160;&#160; RETURN 0 ;\n&#160; END ;\n<\/pre>\n<p class=\"caption\">Listing 1-8: Altering the <span class=\"STCodeinTextChar\">ChangeCodeDescription<\/span> stored procedure so that it retries after a deadlock<\/p>\n<p>Before we run our test, let&#8217;s reset the test data in our <span class=\"STCodeinTextChar\">Codes<\/span> and <span class=\"STCodeinTextChar\">CodeDescriptionsChangeLog<\/span> tables.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">-- reset our test data\nDELETE&#160; FROM dbo.CodeDescriptionsChangeLog ;\nDELETE&#160; FROM dbo.Codes ;\nINSERT&#160; INTO dbo.Codes\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; ( Code, Description )\nVALUES&#160; ( 'IL', 'IL' ) ; \nGO \n\nEXEC dbo.ChangeCodeDescription\n&#160;&#160;&#160; @Code = 'IL',\n&#160;&#160;&#160; @Description = 'Ill.' ; \nGO\n\nSELECT&#160; Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; Description\nFROM&#160;&#160;&#160; dbo.Codes ; \n\nSELECT&#160; Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; OldDescription + ', ' + NewDescription \nFROM&#160;&#160;&#160; dbo.CodeDescriptionsChangeLog ;\n\n(1 row(s) affected)\n\n(1 row(s) affected)\nModifications succeeded\nCode&#160;&#160;&#160;&#160;&#160;&#160; Description\n---------- ----------------------------------------\nIL&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Ill.\n\n(1 row(s) affected)\n\nCode&#160;&#160;&#160;&#160;&#160;&#160; \n---------- -------------------------------------------\nIL&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; IL, Ill.\n\n(1 row(s) affected)\n\n<\/pre>\n<p class=\"caption\">Listing 1-9: Resetting the test data<\/p>\n<p>We&#8217;re now ready to run the test. From one tab in SSMS, we&#8217;ll start a <span class=\"STCodeinTextChar\">SERIALIZABLE<\/span> transaction against the <span class=\"STCodeinTextChar\">CodeDescriptionsChangeLog<\/span> table, as shown in Listing 1-10.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SET DEADLOCK_PRIORITY HIGH ;\nSET TRANSACTION ISOLATION LEVEL SERIALIZABLE ;\nBEGIN TRANSACTION ;\nSELECT * FROM dbo.CodeDescriptionsChangeLog ;\n\n\/*\nUPDATE&#160; dbo.Codes\nSET&#160;&#160;&#160;&#160; Description = 'Illinois'\nWHERE&#160;&#160; Code = 'IL' ; \nCOMMIT ; \n\n*\/\n<\/pre>\n<p class=\"caption\">Listing 1-10: Tab #1, Start a transaction against the <span class=\"STCodeinTextChar\">CodeDescriptionsChangeLog<\/span> table<\/p>\n<p>From a second tab, invoke our stored procedure, as shown in Listing 1-11. The session will &#8216;hang&#8217; in lock waiting mode, due to our <span class=\"STCodeinTextChar\">SERIALIZABLE<\/span> transaction accessing the <span class=\"STCodeinTextChar\">CodeDescriptionsChangeLog<\/span> table.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">EXEC dbo.ChangeCodeDescription\n&#160;&#160;&#160;&#160; @code='IL', \n&#160;&#160;&#160;&#160; @Description='?' ;\n\nSELECT&#160;&#160; Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Description\nFROM&#160;&#160;&#160;&#160; dbo.Codes ; \n\nSELECT&#160;&#160; Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; OldDescription + ', ' + NewDescription\nFROM&#160;&#160;&#160;&#160; dbo.CodeDescriptionsChangeLog ; \n<\/pre>\n<p class=\"caption\">Listing 1-11: Tab #2, Invoke the <span class=\"STCodeinTextChar\">ChangeCodeDescription<\/span> stored procedure <\/p>\n<p>Now return to Tab #1, and execute the commented <span class=\"STCodeinTextChar\">UPDATE<\/span> against the <span class=\"STCodeinTextChar\">Codes<\/span> table, from Listing 1-10, including the <span class=\"STCodeinTextChar\">COMMIT<\/span>. As soon as this code tries to execute, a deadlock is detected. SQL Server chooses our stored procedure execution from Tab #2 as the deadlock victim, since we deliberately contrived for this to be the case. The transaction in our <span class=\"STCodeinTextChar\">TRY<\/span> block is rolled back, but then our <span class=\"STCodeinTextChar\">CATCH<\/span> block is executed and we try to execute our stored procedure again. This time, since Tab #1 has now committed, the modification succeeds. The output from Tab #2 is shown in Listing 1-12.<\/p>\n<pre>Rolling back\nEncountered a deadlock\n\n(1 row(s) affected)\n\n(1 row(s) affected)\nModifications succeeded\nCode&#160;&#160;&#160;&#160;&#160;&#160; Description\n---------- -----------------------------------\nIL&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ?\n\n(1 row(s) affected)\n\nCode&#160;&#160;&#160;&#160;&#160;&#160; \n---------- -----------------------------------\nIL&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; IL, Ill.\nIL&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Illinois, ?\n<\/pre>\n<p class=\"caption\">Listing 1-12. Tab #2, output from execution of the stored procedure<\/p>\n<p>Note also however, that the <span class=\"STCodeinTextChar\">UPDATE<\/span> we execute form Tab#1 is &#8216;lost&#8217;; its changes were overwritten when the re-try succeeded.<\/p>\n<p>From these examples, we have learned the following:<\/p>\n<ul>\n<li>If several modifications must succeed or fail together, use transactions, and roll the modification back, as a unit, if any one of them fails  <\/li>\n<li>Always anticipate that any modification may fail. Use <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> to ensure that transactions roll back after a failure. Alternatively, we can wrap our transactions in <span class=\"STCodeinTextChar\">TRY<\/span> blocks, and roll them back in <span class=\"STCodeinTextChar\">CATCH<\/span> blocks.<\/li>\n<\/ul>\n<p>Unfortunately, there are a few problems with using <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> error handling that we need to discuss. In the next section, we&#8217;ll look at some ways in which <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> error handling is limited and its behavior surprising. We&#8217;ll then see what we can achieve when using C# for error handling, instead of T-SQL.<\/p>\n<h1>TRY&#8230;CATCH Gotchas<\/h1>\n<p>T-SQL is not really an efficient language for error handling, and is certainly less robust than error handling in client side languages such as C++, Java, and C#. As such, although in most cases <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> blocks work as expected and catch errors as they should, there are also quite a few &#8220;special cases&#8221; that we need to know about, where the behavior is not as we might expect.<\/p>\n<p class=\"MsoNormal\">Furthermore, <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> error handling does not really facilitate code reuse. If we want to use this approach in another stored procedure, we cannot fully reuse our T-SQL error handling code; we have to cut and paste much of it into that other stored procedure. This is a recipe for bugs and inconsistencies.<\/p>\n<p>Over the following sections, we&#8217;ll discuss some of the special cases of which we need to be aware, when using <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span>.<\/p>\n<h2>Re-throwing Errors<\/h2>\n<p>In many cases, we do not wish to handle certain errors in our <span class=\"STCodeinTextChar\">CATCH<\/span> block, and instead want to re-throw them, so that they are handled elsewhere. In our previous example, where we wished to retry execution after a deadlock, all other errors were handled by capturing the error message, using the <span class=\"STCodeinTextChar\">ERROR_MESSAGE<\/span> function, and re-throwing the error using <span class=\"STCodeinTextChar\">RAISERROR<\/span>. However, the error message on its own is generally insufficient; we should also retrieve the information from the <span class=\"STCodeinTextChar\">ERROR_LINE<\/span>, <span class=\"STCodeinTextChar\">ERROR_NUMBER<\/span>, <span class=\"STCodeinTextChar\">ERROR_PROCEDURE<\/span>, <span class=\"STCodeinTextChar\">ERROR_SEVERITY<\/span>, and <span class=\"STCodeinTextChar\">ERROR_STATE<\/span> functions, declare variables to store this information, and then use <span class=\"STCodeinTextChar\">RAISERROR<\/span> to re-throw it. This is very verbose and, as we shall see later, we can achieve exactly the same outcome in C# by issuing one single command: <span class=\"STCodeinTextChar\">throw<\/span>.<\/p>\n<p>However, the real problem with the <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> approach is this: <span class=\"STCodeinTextChar\">RAISERROR<\/span> cannot preserve <span class=\"STCodeinTextChar\">ERROR_NUMBER<\/span>, so when we re-throw an error we often change its error code. For example, consider the <span class=\"STCodeinTextChar\">ConversionErrorDemo<\/span> stored procedure in Listing 1-13. It attempts to cast a string as an integer in the <span class=\"STCodeinTextChar\">TRY<\/span> block, and then in the <span class=\"STCodeinTextChar\">CATCH<\/span> block invokes two of the seven error handling functions and re-throws the error.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">CREATE PROCEDURE dbo.ConversionErrorDemo\nAS \n&#160;&#160;&#160; BEGIN TRY ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT&#160; CAST('abc' AS INT) ;\n&#160;&#160;&#160; -- some other code\n&#160;&#160;&#160; END TRY\n&#160;&#160;&#160; BEGIN CATCH ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; DECLARE @ErrorNumber INT ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @ErrorMessage NVARCHAR(4000) ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT&#160; @ErrorNumber = ERROR_NUMBER() ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @ErrorMessage = ERROR_MESSAGE() ; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; IF @ErrorNumber = 245 \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; BEGIN ;\n&#160;&#160;&#160; -- we shall not handle conversion errors here\n&#160;&#160;&#160; -- let us try to rethrow the error, so that \n&#160;&#160;&#160; -- it is handled elsewhere.\n&#160;&#160;&#160; -- This error has number 245, but we cannot\n&#160;&#160;&#160; -- have RAISERROR keep the number of the error.\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; RAISERROR(@ErrorMessage, 16, 1) ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; END ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; ELSE \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; BEGIN ;\n&#160;&#160;&#160; -- handle all other errors here\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT&#160; @ErrorNumber AS ErrorNumber ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @ErrorMessage AS ErrorMessage ; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; END ;\n&#160;&#160;&#160; END CATCH ;\nGO\nEXEC dbo.ConversionErrorDemo ;\n\n(0 row(s) affected)\nMsg 50000, Level 16, State 1, Procedure ConversionErrorDemo, Line 19\nConversion failed when converting the varchar value 'abc' to data type int.\n<\/pre>\n<p class=\"caption\">Listing 1-13: An error with error number 245, which gets a different <span class=\"STCodeinTextChar\">ERROR_NUMBER<\/span>, 50000, when re-thrown.<\/p>\n<p>The fact that re-thrown errors get a different error number means that, when we actually come to handling conversion errors, both re-thrown and original, we cannot catch then using the &#160;error number alone, as shown in Listing 1-14.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">BEGIN TRY ;\n&#160;&#160;&#160; EXEC dbo.ConversionErrorDemo ;\n&#160;&#160;&#160; -- some other code\nEND TRY\nBEGIN CATCH ;\n&#160;&#160;&#160; DECLARE @ErrorNumber INT ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; @ErrorMessage NVARCHAR(4000) ;\n&#160;&#160;&#160; SELECT&#160; @ErrorNumber = error_number() ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @ErrorMessage = error_message() ; \n&#160;&#160;&#160; IF @ErrorNumber = 245 \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; BEGIN ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; PRINT 'Conversion error caught';\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; END ;\n&#160;&#160;&#160; ELSE \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; BEGIN ;\n&#160;&#160;&#160; -- handle all other errors here\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; PRINT 'Some other error caught';\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT&#160; @ErrorNumber AS ErrorNumber ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @ErrorMessage AS ErrorMessage ; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; END ;\nEND CATCH ;\nGO\n\nSome other error caught\nErrorNumber ErrorMessage\n----------- -----------------\n50000&#160;&#160;&#160;&#160;&#160;&#160; Conversion failed when converting the varchar value 'abc'\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;to data type int.\n<\/pre>\n<p class=\"caption\">Listing 1-14: The re-thrown error is no longer assigned number 245.<\/p>\n<p>To catch both the original and re-thrown error, we need to parse the error message, as shown in Listing 1-15.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">BEGIN TRY ;\n&#160;&#160;&#160; EXEC dbo.ConversionErrorDemo ;\n&#160;&#160;&#160; -- some other code\nEND TRY\nBEGIN CATCH ;\n&#160;&#160;&#160; DECLARE @ErrorNumber INT ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; @ErrorMessage NVARCHAR(4000) ;\n&#160;&#160;&#160; SELECT&#160; @ErrorNumber = ERROR_NUMBER() ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @ErrorMessage = ERROR_MESSAGE() ; \n&#160;&#160;&#160; IF @ErrorNumber = 245\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; OR @ErrorMessage LIKE '%Conversion failed when\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; converting %' \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; BEGIN ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; PRINT 'Conversion error caught' ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; END ;\n&#160;&#160;&#160; ELSE \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; BEGIN ;\n&#160;&#160;&#160; -- handle all other errors here\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; PRINT 'Some other error caught' ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT&#160; @ErrorNumber AS ErrorNumber ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @ErrorMessage AS ErrorMessage ; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; END ;\nEND CATCH ;\n<\/pre>\n<p class=\"caption\">Listing 1-15: Parsing the error message to catch a re-thrown error.<\/p>\n<p>Although. This time, we did catch our re-thrown error, our method is not robust: we can by mistakenly catch other errors and handle them as if they were conversion errors, as shown in Listing 1-16.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">BEGIN TRY ;\n&#160;&#160;&#160; RAISERROR('Error saving ticket %s',16,1,\n&#160;&#160;&#160; 'Saving discount blows up: ''Conversion failed when\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; converting ...''') ;\n&#160;&#160;&#160; -- some other code\nEND TRY\nBEGIN CATCH ;\n&#160;&#160;&#160; DECLARE @ErrorNumber INT ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; @ErrorMessage NVARCHAR(4000) ;\n&#160;&#160;&#160; SELECT&#160; @ErrorNumber = ERROR_NUMBER() ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @ErrorMessage = ERROR_MESSAGE() ; \n&#160;&#160;&#160; IF @ErrorNumber = 245\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; OR @ErrorMessage LIKE '%Conversion failed when\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; converting %' \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; BEGIN ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; PRINT 'Conversion error caught' ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; END ;\n&#160;&#160;&#160; ELSE \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; BEGIN ;\n&#160;&#160;&#160; -- handle all other errors here\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; PRINT 'Some other error caught' ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT&#160; @ErrorNumber AS ErrorNumber ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @ErrorMessage AS ErrorMessage ; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; END ;\nEND CATCH ;\nGO\n\nConversion error caught\n<\/pre>\n<p class=\"caption\">Listing 1-16: Incorrectly handling a ticket-saving error as if it were a conversion error.<\/p>\n<p>As we have seen, the inability of T-SQL to re-throw errors may prevent us from robustly handling re-thrown errors. If we need to re-throw errors, we should do it on the client.<\/p>\n<h2>TRY&#8230;CATCH Blocks Cannot Catch all Errors<\/h2>\n<p>Interestingly enough, sometimes <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> blocks just do not catch errors. Sometimes, this represents &#8220;expected behavior&#8221;; in other words, the behavior is documented and the reason why the error is not caught, for example when a connection fails, is intuitive. However, in some other cases the behavior, while still documented, can be quite surprising.<\/p>\n<p>In either case, however, it means that we cannot assume that all errors originating in the database can or will be handled in a <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span>. Whenever we issue an SQL statement from the client, we need to be aware that it can generate an exception, and we need to be ready to handle it on the client, in case the <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> blocks that we use in our T-SQL code doesn&#8217;t catch it.<\/p>\n<h3>Killed Connections and Timeouts<\/h3>\n<p>In some cases, it is the expected behavior that errors cannot be caught by <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> blocks. For example, if your connection is killed, it is documented and well known that your <span class=\"STCodeinTextChar\">CATCH<\/span> block will not catch and handle it.<\/p>\n<p>Also, we need to be aware of &#8216;attentions&#8217;, also known as &#8216;timeouts&#8217;, as they also cannot be caught by <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> blocks, and this is also the expected behavior. To demonstrate this, start the script in Listing 1-17, but cancel its execution immediately by pressing the &#8220;Cancel Executing Query&#8221; button.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SET XACT_ABORT OFF;\nBEGIN TRY ;\n&#160; PRINT 'Beginning TRY block' ; \n&#160; BEGIN TRANSACTION ; \n&#160; WAITFOR DELAY '00:10:00' ; \n&#160; COMMIT ; \n&#160; PRINT 'Ending TRY block' ; \nEND TRY \nBEGIN CATCH ;\n&#160; PRINT 'Entering CATCH block' ; \nEND CATCH ; \nPRINT 'After the end of the CATCH block' ;&#160; \n\nBeginning TRY block\nQuery was cancelled by user.\n<\/pre>\n<p class=\"caption\">Listing 1-17: <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> behavior when a timeout occurs<\/p>\n<p>The execution stops immediately, without executing the <span class=\"STCodeinTextChar\">CATCH<\/span> block. Listing 1-18 demonstrates that the connection is still in the middle of an outstanding transaction.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT&#160; @@TRANCOUNT AS [@@TRANCOUNT] ;\nROLLBACK ;\n\n@@TRANCOUNT\n-----------\n1\n\n(1 row(s) affected)\n<\/pre>\n<p class=\"caption\">Listing 1-18: The connection is in the middle of an outstanding transaction<\/p>\n<p>If the client initiates a timeout, the behavior is exactly the same: the execution stops immediately, the outstanding transaction is neither committed nor rolled back, and an unhandled exception is sent to the client. This is simply how timeouts work and the only way to avoid this behavior is to turn it off altogether. For instance, we can turn off timeouts in ADO.NET by setting the <span class=\"STCodeinTextChar\">CommandTimeout<\/span> property to 0.&#160; Of course, we can turn <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> on, in which case at least the transaction will be rolled back. The <span class=\"STCodeinTextChar\">CATCH<\/span> block, however, will still be bypassed.<\/p>\n<h3>Problems with TRY&#8230;CATCH Scope<\/h3>\n<p>In some cases, the behavior is <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> is documented, but will be surprising to developers used to error handling in languages such as C#.<\/p>\n<p>Listing 1-19 demonstrates a simple case of a query, wrapped in a <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span>, which tries to use a temporary table that does not exist. However, the <span class=\"STCodeinTextChar\">CATCH<\/span> block is not executed, and we get an unhandled exception.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">BEGIN TRY ;\n&#160; PRINT 'Beginning TRY block' ; \n&#160; SELECT&#160; COUNT(*)\n&#160; FROM&#160;&#160;&#160; #NoSuchTempTable ; \n&#160; PRINT 'Ending TRY block' ; \nEND TRY&#160; \nBEGIN CATCH ;\n&#160; PRINT 'Entering CATCH block' ; \nEND CATCH ; \nPRINT 'After the end of the CATCH block' ;\n\nBeginning TRY block\nMsg 208, Level 16, State 0, Line 3\nInvalid object name '#NoSuchTempTable'.\n<\/pre>\n<p class=\"caption\">Listing 1-19: Sometimes a <span class=\"STCodeinTextChar\">CATCH<\/span> block is bypassed when an error occurs<\/p>\n<p>Even more surprising for object-oriented developers is that this is not a bug; it is the just the way SQL Server works in this case. According to MSDN for SQL Server 2008, <\/p>\n<p><i>&#8220;Errors that occur during statement-level recompilation&#8230;are not handled by a <span class=\"STCodeinTextChar\">CATCH<\/span> block when they occur at the same level of execution as the <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> construct&#8221;.<\/i><\/p>\n<p>The issue here is that compilation errors that occur at run-time (as a result of deferred name resolution) abort the rest of the scope, which is equal to the batch in directly submitted SQL, but only equal to the rest of the procedure in a stored procedure or function. So a <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> at the same scope will not intercept these errors, but a <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> on a different scope (regardless of being nested or not) will catch it.<\/p>\n<p class=\"MsoNormal\">My point here is simple: SQL Server does not always handle errors in a way object oriented languages do. If we choose to use the error handling provided by SQL Server, we really need to learn it in detail or we will be in for some unpleasant surprises.<\/p>\n<h3>Doomed Transactions<\/h3>\n<p>There is another serious problem with T-SQL <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> blocks: in some cases an error that occurred inside a <span class=\"STCodeinTextChar\">TRY<\/span> block is considered so severe that the whole transaction is doomed, or, in other words, it cannot be committed. Theoretically, the concept of doomed transactions makes perfect sense. Unfortunately, some really trivial errors, such as conversion errors, render transactions doomed if we use <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> provided by T-SQL. For example, consider the transactions shown in Listing 1-20. The first attempts to perform a 1\/0 calculation and the second to convert a strong to an integer. We do not want to roll back the whole transaction if an error occurs, so we set <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> to <span class=\"STCodeinTextChar\">OFF<\/span>.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SET XACT_ABORT OFF ;\nSET NOCOUNT ON ;\n\nBEGIN TRANSACTION ;\nSELECT&#160; 1 ;\nGO\nBEGIN TRY ;\n&#160;&#160;&#160; SELECT&#160; 1 \/ 0 ;\nEND TRY\nBEGIN CATCH\n&#160;&#160;&#160; PRINT 'Error occurred' ;\n&#160;&#160;&#160; SELECT error_message() AS ErrorMessage ;\nEND CATCH ;\nGO\nIF @@TRANCOUNT &lt;&gt; 0 \n&#160;&#160;&#160; BEGIN ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; COMMIT ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; PRINT 'Committed' ;\n&#160;&#160;&#160; END ;\nGO\n\nBEGIN TRANSACTION ;\nSELECT&#160; 1 ;\nGO\nBEGIN TRY ;\n&#160;&#160;&#160; SELECT&#160; cast('abc' AS INT ) ;\nEND TRY\nBEGIN CATCH\n&#160;&#160;&#160; PRINT 'Error occurred' ;\n&#160;&#160;&#160; SELECT error_message() AS ErrorMessage ;\nEND CATCH ;\nGO\nIF @@TRANCOUNT &lt;&gt; 0 \n&#160;&#160;&#160; BEGIN ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; COMMIT ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; PRINT 'Committed' ;\n&#160;&#160;&#160; END ;\n\n\n-----------\n1\n\n\n-----------\n\nError occurred\nErrorMessage\n-------------------------------------\nDivide by zero error encountered.\n\nCommitted\n\n-----------\n1\n\n\n-----------\n\nError occurred\nErrorMessage\n-------------------------------------\nConversion failed when converting the varchar value 'abc' to data type int.\n\nMsg 3998, Level 16, State 1, Line 1\nUncommittable transaction is detected at the end of the batch. The transaction is rolled back.\n<\/pre>\n<p class=\"caption\">Listing 1-20: A transaction is doomed after a trivial error such as a conversion error.<\/p>\n<p>As the output demonstrates, we can commit a transaction after a divide by zero, but a conversion error renders the transaction doomed, and therefore un-commitable. The latter case demonstrates that even a seemingly-trivial conversion error considered severe enough to override the <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> setting, and the whole transaction is automatically rolled back. <\/p>\n<p>To determine whether or not our transaction is committable, within <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span>, we can use the <span class=\"STCodeinTextChar\">XACT_STATE()<\/span> function, as demonstrated in listing 1-21.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">BEGIN TRY ;\n&#160; BEGIN TRANSACTION ;\n&#160; SELECT&#160; CAST ('abc' AS INT) ;\n&#160; COMMIT ;\n&#160; PRINT 'Ending try block' ;\nEND TRY\nBEGIN CATCH ;\n&#160; PRINT 'Entering CATCH block' ;\n&#160; IF XACT_STATE () = 1 \n&#160;&#160;&#160; BEGIN ;\n&#160;&#160;&#160;&#160;&#160; PRINT 'Transaction is committable' ;\n&#160;&#160;&#160;&#160;&#160; COMMIT ;\n&#160;&#160;&#160; END ; \n&#160; IF XACT_STATE () = -1 \n&#160;&#160;&#160; BEGIN ;\n&#160;&#160;&#160;&#160;&#160; PRINT 'Transaction is not committable' ;\n&#160;&#160;&#160;&#160;&#160; ROLLBACK ;\n&#160;&#160;&#160; END ;\nEND CATCH ;\nPRINT 'Ending batch' ;\nGO\nSELECT&#160; @@TRANCOUNT AS [@@TRANCOUNT] ;\nBEGIN TRY ;\n&#160; BEGIN TRANSACTION ;\n&#160; SELECT&#160; 1 \/ 0 ;\n&#160; COMMIT ;\n&#160; PRINT 'Ending try block' ;\nEND TRY\nBEGIN CATCH ;\n&#160; PRINT 'Entering CATCH block' ;\n&#160; IF XACT_STATE () = 1 \n&#160;&#160;&#160; BEGIN ;\n&#160;&#160;&#160;&#160;&#160; PRINT 'Transaction is committable' ;\n&#160;&#160;&#160;&#160;&#160; COMMIT ;\n&#160;&#160;&#160; END ; \n&#160; IF XACT_STATE () = -1 \n&#160;&#160;&#160; BEGIN ;\n&#160;&#160;&#160;&#160;&#160; PRINT 'Transaction is not committable' ;\n&#160;&#160;&#160;&#160;&#160; ROLLBACK ;\n&#160;&#160;&#160; END ;\nEND CATCH ;\nPRINT 'Ending batch' ;\nGO\n&#160;\n(0 row(s) affected)\nEntering CATCH block\nTransaction is not committable\nEnding batch\n\n(1 row(s) affected)\n\n(0 row(s) affected)\nEntering CATCH block\nTransaction is committable\nEnding batch\n<\/pre>\n<p class=\"caption\">Listing 1-21: Using <span class=\"STCodeinTextChar\">xact_state<\/span> to determine if our transaction is committable or doomed<\/p>\n<p>Clearly there are situations where the concept of a doomed transaction makes sense. For example, if the server runs out of disk space while running a transaction then there is no way the transaction could complete. Unfortunately, the current implementation of SQL Server sometimes dooms transactions for very trivial reasons. In all-too-many cases this peculiar behavior of SQL Server makes it impossible to develop feature rich error handling in T-SQL, because if a transaction is doomed, we have no choice other than to roll it back. <\/p>\n<p>We will not cover any examples here, but this can also cause problems when attempting to use <span class=\"STCodeinTextChar\">SAVEPOINT<\/span>s. Consider the following, very common, requirement: <\/p>\n<p><i>&#8220;If our stored procedure is invoked in the middle of an outstanding transaction, and if any command in our stored procedure fails, undo only the changes made by the stored procedure. Do not make any decisions regarding the changes done outside of our stored procedure&#8221;.<\/i><\/p>\n<p>Unfortunately, there is no robust way to implement such requirements in T-SQL using a <span class=\"STCodeinTextChar\">SAVEPOINT<\/span>. While it will work in most cases, it will not work as intended when a transaction is doomed.<\/p>\n<h1>Client-side Error Handling<\/h1>\n<p>In order to overcome the described limitations and difficulties with error handling using SQL Server&#8217;s <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span>, my advice is simple: when we need to implement feature-rich error handling, to respond intelligently to an anticipated error, we should do it in a language that offers more robust error handling, such as C#.<\/p>\n<p>By doing so, we avoid complications caused by doomed transactions (for example, trivial conversion errors in a C# <span class=\"STCodeinTextChar\">TRY<\/span> block will never doom a transaction), or by error numbers being changed when they are re-thrown, and so on. Furthermore, once error handling is implemented in a C# class it can be re-used by all modules that need it, so we promote code reuse to its fullest extent.<\/p>\n<p>Nowadays many of us developers use more than one language in our daily activities, and the reason is very simple and very pragmatic: in many cases it is much easier to learn a new language to accomplish a specific task, to which the language is well-suited, than it is to try to &#8216;bend&#8217; a single language to all purposes.<\/p>\n<p>By way of an example, Listing 1-22 re-implements in C# our &#8220;retry after deadlock&#8221; logic, from Listing 1-8. We need only implement this logic once, and we can use this class to execute any command against SQL Server.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">&#160;&#160;&#160; class SqlCommandExecutor\n&#160;&#160;&#160; {\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; public static void RetryAfterDeadlock\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; (SqlCommand command, int timesToRetry)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; {\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; int retryCount = 0;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; while (retryCount &lt; timesToRetry)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; {\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; retryCount++;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; try\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; {\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; command.ExecuteNonQuery();\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Console.WriteLine\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; (\"Command succeeded:\" + \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; command.CommandText);\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; return;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; catch (SqlException e)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; {\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; if (e.Number != 1205)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; {\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; throw;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Console.WriteLine(\"Retrying after deadlock:\" + command.CommandText);\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; }\n&#160;&#160;&#160; }\n<\/pre>\n<p class=\"caption\">Listing 1-22. Implementing the &#8220;retry after deadlock&#8221; logic in a C# class.<\/p>\n<p class=\"MsoNormal\">Let&#8217;s try this class out. First of all, we need to remove the retry logic from our <span class=\"STCodeinTextChar\">ChangeCodeDescription<\/span> stored procedure, but keep it just as prone to deadlocks as before. Listing 1-23 shows how to accomplish that.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">ALTER PROCEDURE dbo.ChangeCodeDescription\n&#160;&#160;&#160; @Code VARCHAR(10) ,\n&#160;&#160;&#160; @Description VARCHAR(40)\nAS \n&#160;&#160;&#160; BEGIN ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; DECLARE @OldDescription VARCHAR(40) ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; SET DEADLOCK_PRIORITY LOW ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; SET XACT_ABORT ON ; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; BEGIN TRANSACTION ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; SET @OldDescription = ( SELECT&#160; Description\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM&#160;&#160;&#160; dbo.Codes\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE&#160;&#160; Code = @Code\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ) ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; UPDATE&#160; dbo.Codes\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; SET&#160;&#160;&#160;&#160; Description = @Description\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE&#160;&#160; Code = @Code ; \n\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; INSERT&#160; INTO dbo.CodeDescriptionsChangeLog\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ( Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ChangeDate ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; OldDescription ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; NewDescription\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; )\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT&#160; @Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; current_timestamp ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @OldDescription ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @Description ; \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; PRINT 'Modifications succeeded' ;\n\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; COMMIT ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; RETURN 0 ;\n&#160;&#160;&#160; END ;\n<\/pre>\n<p class=\"caption\">Listing 1-23: Removing the retry logic from the <span class=\"STCodeinTextChar\">ChangeCodeDescription<\/span> stored procedure.<\/p>\n<p>Obviously we&#8217;d first need to test this procedure and verify that it can successfully complete; a step that I will leave as a simple exercise. <\/p>\n<p>In order to test what happens when we have a deadlock, we need to first reset our test data by rerunning script 1-9. Next, start a <span class=\"STCodeinTextChar\">SERIALIZABLE<\/span> transaction against the <span class=\"STCodeinTextChar\">CodeDescriptionsChangeLog<\/span> table, by running the script in Listing 1-10.<\/p>\n<p>Rather than invoke our <span class=\"STCodeinTextChar\">ChangeCodeDescription<\/span> stored procedure forma&#160; second SSMS session, as before, we need to execute the C# code shown in Listing 1-24, which invokes the same stored procedure through our <span class=\"STCodeinTextChar\">RetryAfterDeadlock<\/span> method. <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">&#160;&#160;&#160; class RetryAfterDeadlockDemo\n&#160;&#160;&#160; {\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; static void Main(string[] args)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; {\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; try\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; {\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; using (SqlConnection connection =\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; new SqlConnection\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; (\"server=(local);\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; trusted_connection=true;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; database=test8;\"))\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; {\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; connection.Open();\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SqlCommand command = \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; connection.CreateCommand();\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; command.CommandText = \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; \"EXEC dbo.ChangeCodeDescription\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;@code='IL', @Description='?' ;\";\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;command.CommandType = CommandType.Text;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160;SqlCommandExecutor.\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; RetryAfterDeadlock(command, 3);\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Console.WriteLine(\"Command succeeded\");\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; catch (Exception e)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; {\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Console.WriteLine(\"Error in Main:\" + e);\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; }\n&#160;&#160;&#160; }\n<\/pre>\n<p class=\"caption\">Listing 1-24: Using the <span class=\"STCodeinTextChar\">RetryAfterDeadlock<\/span> method to invoke our stored procedure.<\/p>\n<p>This method will not complete, as the table is locked by our SSMS transaction. Return to SSMS and highlight and execute the commented code, both the <span class=\"STCodeinTextChar\">UPDATE<\/span> command and the <span class=\"STCodeinTextChar\">COMMIT<\/span>. The transaction invoked from C# will be chosen as a deadlock victim and it will retry, and there is enough debugging output in our C# code to demonstrate what is happening.<\/p>\n<p>Finally, let us verify that, after the retry, the modification completed, as shown in Listing 1-25.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">EXEC dbo.ChangeCodeDescription @code='IL', \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; @Description='?' ;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; \nSELECT&#160;&#160; Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Description\nFROM&#160;&#160;&#160;&#160; dbo.Codes ; \n\nSELECT&#160;&#160; Code ,\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; OldDescription + ', ' + NewDescription\nFROM&#160;&#160;&#160;&#160; dbo.CodeDescriptionsChangeLog ; \n\nCode&#160;&#160;&#160;&#160;&#160;&#160; Description\n---------- -----------\nIL&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ?\n\n(1 row(s) affected)\n\nCode&#160;&#160;&#160;&#160;&#160;&#160; \n---------- -------\nIL&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; IL, Ill.\nIL&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; Illinois, ?\n<\/pre>\n<p class=\"caption\">Listing 1-25: Checking that the data is in the expected state.<\/p>\n<p>In short, C# allows us to implement our &#8220;retry after deadlock&#8221; logic just once and reuse it as many times as we need. As defensive programmers, we really want to reuse our code, not to cut and paste the same code all over our systems and so we have a strong motivation to use a good modern tool such as C# for our error handling. <\/p>\n<p>My message here is quite moderate. I am not suggesting that we abandon T-SQL error handling; far from it. In the simplest cases, when all we need is to roll back and raise an error, we should use <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> and transactions. Notice that in Listing 1-23, we use <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> and a transaction to roll back after a deadlock, but we implement all of the more complex error handling logic in C#.<\/p>\n<p>Of course, there are situations when we do need to implement error handling in T-SQL. Whenever we are considering such an option, we need to realize that error handling in T-SQL is very complex and not really intuitive to a developer with experience in other languages. Also, it has a lot of gotchas, and it lacks some features which client side programmers consider as their birthright, such as the ability to re-throw an error exactly as it was caught. <\/p>\n<h1>Conclusion<\/h1>\n<p>It is essential that the defensive database programmer includes robust error handling in all production T-SQL code. However, as much as the introduction of <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span> has improved error handling in T-SQL, it still lacks the versatility, elegance and ease of use that is typical of client-side languages such as Java and C#. Ultimately, you will find that it is not possible to handle certain errors in Transact SQL at all and that we need to complement our T-SQL error handling with error handling on the client.<\/p>\n<p class=\"MsoNormal\">I hope this article has taught you the following specific lessons in defensive error handling:<\/p>\n<ul>\n<li>If you already use a modern language such as C# in your system, then it makes sense to utilize it to do complex handling of errors related to the database  <\/li>\n<li>If handling errors on SQL Server, keep it simple where possible; set <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> to <span class=\"STCodeinTextChar\">ON<\/span> and use transactions in order to roll back and raise an error  <\/li>\n<li>If you wish to use <span class=\"STCodeinTextChar\">TRY&#8230;CATCH<\/span>, learn it thoroughly, and watch out in particular for the following problems:\n<ul>\n<li>One and the same code may run differently depending on <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> setting  <\/li>\n<li>We cannot re-throw errors exactly as we catch them  <\/li>\n<li><span class=\"STCodeinTextChar\">CATCH<\/span> blocks do not catch all errors  <\/li>\n<li>Some errors do not respect <span class=\"STCodeinTextChar\">XACT_ABORT<\/span> settings  <\/li>\n<li>Some transaction may be rendered un-committable, <span class=\"STItalic\">a.k.a.<\/span> doomed<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p class=\"note\">This article is an extract from Alex&#8217;s book &#8216;Defensive Database Programming&#8217; <a href=\"http:\/\/www.amazon.com\/Defensive-Database-Programming-SQL-Server\/dp\/1906434492\/\">available from Amazon now<\/a>.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>TRY&#8230;CATCH error handling in SQL Server has certain limitations and inconsistencies that will trap the unwary developer, used to the more feature-rich error handling of client-side languages such as C# and Java. In this article, abstracted from his excellent new book, Defensive Database Programming with SQL Server, Alex Kuznetsov offers a simple, robust approach to checking and handling errors in SQL Server, with client-side error handling used to enforce what is done on the server.&hellip;<\/p>\n","protected":false},"author":6776,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143531],"tags":[5119,4371,4168,5225,5298,4933,4150,4151,4252],"coauthors":[],"class_list":["post-1019","post","type-post","status-publish","format-standard","hentry","category-t-sql-programming-sql-server","tag-alex-kuznetsov","tag-c","tag-database","tag-defensive-database-programming","tag-defensive-error-handling","tag-ebook","tag-sql","tag-sql-server","tag-t-sql-programming"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1019","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/6776"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=1019"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1019\/revisions"}],"predecessor-version":[{"id":40426,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1019\/revisions\/40426"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=1019"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=1019"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=1019"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=1019"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}