{"id":99,"date":"2015-01-14T00:00:00","date_gmt":"2013-01-01T00:00:00","guid":{"rendered":"https:\/\/www.sqlhammer.com\/blog\/how-to-log-errors-with-t-sql-trycatch"},"modified":"2026-03-18T21:51:32","modified_gmt":"2026-03-18T21:51:32","slug":"how-to-log-errors-with-t-sql-trycatch","status":"publish","type":"post","link":"https:\/\/sqlhammer.com\/index.php\/2015\/01\/14\/how-to-log-errors-with-t-sql-trycatch\/","title":{"rendered":"How to log errors with T-SQL try\/catch"},"content":{"rendered":"<p>Today a co-worker of mine (Brad) asked a question regarding the usage of T-SQL\u2019s <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms175976.aspx\">Try-Catch<\/a> block. Brad wanted to write to a table every time a stored procedure failed so that it was easy to recover the error messages and track issues. His Try-Catch block looked something like this.<\/p>\n<pre><code class=\"language-\">BEGIN TRY\n\n<p>BEGIN TRANSACTION<\/p>\n\n<p>PRINT 'do work'<\/p>\n\n<p>COMMIT<\/p>\n\n<p>END TRY\nBEGIN CATCH<\/p>\n\n<p>DECLARE @errMsg VARCHAR(500) = ERROR_MESSAGE()\n, @errState INT = ERROR_STATE()\n, @errSeverity int = ERROR_SEVERITY()<\/p>\n\n<p>INSERT INTO dbo.Failures (errorMsg)\nVALUES (@errMsg)<\/p>\n\n<p>RAISERROR(@errMsg, @errSeverity, @errState);<\/p>\n\n<p>END CATCH<\/code><\/pre>\n<\/p>\n<p>The intention was that the RAISERROR command would be similar to a re-throw and roll back the transaction after writing to the table. Quick tangent, SQL Server 2012\u2019s new THROW command is preferred, in my opinion, over RAISERROR. Check out more on THROW <a href=\"http:\/\/www.sqlshack.com\/background-exception-handling-sql-server-2012\/\">here<\/a>. But I digress. The problem with the above code was that we knew deadlocks were occurring but there was never a single insert into the dbo.Failures table.<\/p>\n<p>Before announcing the solution, let\u2019s set up\u00a0a test for demonstration purposes.<\/p>\n<h2>The test<\/h2>\n<h3>Setup<\/h3>\n<p>This quick script will create our table with data and our table which will be used to log errors.<\/p>\n<pre><code class=\"language-\">CREATE TABLE dbo.ToBeBlocked (id TINYINT NOT NULL)\nCREATE TABLE dbo.Failures (id TINYINT IDENTITY(1,1) NOT NULL\n, errorMsg VARCHAR(500) NOT NULL)\nGO\nINSERT INTO dbo.ToBeBlocked (id) VALUES (1);\nINSERT INTO dbo.ToBeBlocked (id) VALUES (2);\nGO 10<\/code><\/pre>\n<h3>Script 1<\/h3>\n<p>In this script we have the Try-Catch block seen above. I\u2019ve added a select and an update to help us setup a deadlock condition. The DEADLOCK_PRIORITY and REPEATABLE READ isolation level ensures that we take a lock on all rows in the table and try to update them after another script has stepped in the way. The WAITFOR DELAY is used to give time for you to run Script 2 in another query window before the update fires. Finally, it will query the Failures table to see if we inserted any rows.<\/p>\n<pre><code class=\"language-\">SET DEADLOCK_PRIORITY LOW\n\n<p>SET TRANSACTION ISOLATION LEVEL REPEATABLE READ<\/p>\n\n<p>BEGIN TRY<\/p>\n\n<p>BEGIN TRANSACTION<\/p>\n\n<p>SELECT id FROM dbo.ToBeBlocked;<\/p>\n\n<p>-- Give us a chance to run the other script\n-- to cause a deadlock\nWAITFOR DELAY '00:00:05';<\/p>\n\n<p>UPDATE dbo.ToBeBlocked\nSET id = 3\nWHERE id = 2<\/p>\n\n<p>COMMIT<\/p>\n\n<p>END TRY\nBEGIN CATCH<\/p>\n\n<p>DECLARE @errMsg VARCHAR(500) = ERROR_MESSAGE()\n, @errState INT = ERROR_STATE()\n, @errSeverity int = ERROR_SEVERITY()<\/p>\n\n<p>INSERT INTO dbo.Failures (errorMsg)\nVALUES (@errMsg)<\/p>\n\n<p>RAISERROR(@errMsg, @errSeverity, @errState);<\/p>\n\n<p>END CATCH<\/p>\n\n<p>SELECT * FROM dbo.Failures<\/code><\/pre>\n<\/p>\n<h3>Script 2<\/h3>\n<p>This script will be executed during the 5 sec delay from running Script 1. This will cause our deadlock and become the victor because Script 1\u2019s DEADLOCK_PRIORITY is set to LOW, making it the victim.<\/p>\n<pre><code class=\"language-\">BEGIN TRANSACTION\n\n<p>UPDATE dbo.ToBeBlocked\nSET id = 2\nWHERE id = 2;<\/p>\n\n<p>COMMIT<\/code><\/pre>\n<\/p>\n<h2>The results<\/h2>\n<p>By executing Script 1 and causing a deadlock with Script 2, we receive these messages.<\/p>\n<p>> (20 row(s) affected)<br \/>\n><br \/>\n><br \/>\n> (0 row(s) affected)<br \/>\n><br \/>\n> Msg 3930, Level 16, State 1, Line 28<br \/>\n><br \/>\n>  The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.<br \/>\n><br \/>\n>  Msg 50000, Level 13, State 45, Line 31<br \/>\n><br \/>\n>  Transaction (Process ID 52) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.<br \/>\n><br \/>\n><br \/>\n> (0 row(s) affected)<br \/>\n><br \/>\n> Msg 3998, Level 16, State 1, Line 1<br \/>\n><br \/>\n>  Uncommittable transaction is detected at the end of the batch. The transaction is rolled back.<\/p>\n<p>We can also see that no records were inserted into the Failures table.<\/p>\n<p><a href=\"http:\/\/www.sqlhammer.com\/blog\/wp-content\/uploads\/2015\/01\/try-catch-result-1.jpg\"><img decoding=\"async\" src=\"http:\/\/www.sqlhammer.com\/blog\/wp-content\/uploads\/2015\/01\/try-catch-result-1.jpg\" alt=\"\" \/><\/a><\/p>\n<h2>The solution<\/h2>\n<p>The reason that Brad was left without any log records was because the INSERT INTO dbo.Failures was still part of the first, uncommitted, transaction. Since that transaction was deadlocked and could not be committed the, \u201cUncommittable transaction is detected,\u201d error was thrown and everything was rolled back, including the Failures record. The solution to this problem is to add a ROLLBACK statement before the INSERT. This will rollback the transaction which is in an uncommittable state and then allow the Failures record to be INSERTed as a separate transaction.<\/p>\n<pre><code class=\"language-\">BEGIN CATCH\n\n<p>DECLARE @errMsg VARCHAR(500) = ERROR_MESSAGE()\n, @errState INT = ERROR_STATE()\n, @errSeverity int = ERROR_SEVERITY()<\/p>\n\n<p>ROLLBACK --Solution<\/p>\n\n<p>INSERT INTO dbo.Failures (errorMsg)\nVALUES (@errMsg)<\/p>\n\n<p>RAISERROR(@errMsg, @errSeverity, @errState);<\/p>\n\n<p>END CATCH<\/code><\/pre>\n<\/p>\n<h3>New results<\/h3>\n<p>> (20 row(s) affected)<br \/>\n><br \/>\n><br \/>\n> (0 row(s) affected)<br \/>\n><br \/>\n><br \/>\n> (1 row(s) affected)<br \/>\n><br \/>\n> Msg 50000, Level 13, State 45, Line 33<br \/>\n><br \/>\n> Transaction (Process ID 52) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.<br \/>\n><br \/>\n><br \/>\n> (1 row(s) affected)<\/p>\n<p><a href=\"http:\/\/www.sqlhammer.com\/blog\/wp-content\/uploads\/2015\/01\/try-catch-result-2.jpg\"><img decoding=\"async\" src=\"http:\/\/www.sqlhammer.com\/blog\/wp-content\/uploads\/2015\/01\/try-catch-result-2.jpg\" alt=\"\" \/><\/a><\/p>\n<hr \/>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/hammer-derik-172x140.png\" alt=\"\" \/><strong>Written by Derik Hammer of <a href=\"http:\/\/www.sqlhammer.com\/\">SQL Hammer<\/a><\/strong><\/p>\n<p>Derik is a data professional focusing on Microsoft SQL Server. His passion focuses around <a href=\"http:\/\/www.sqlhammer.com\/blog\/category\/high-availability\/\">high-availability<\/a>, <a href=\"http:\/\/www.sqlhammer.com\/blog\/category\/general\/disaster-recovery\/\">disaster recovery<\/a>, continuous integration, and automated maintenance. his experience has spanned long-term database administration, consulting, and\u00a0entrepreneurial ventures.<\/p>\n<p>Derik gives the SQL community credit for plugging the gaps in his knowledge when he was a junior DBA and, now that his skills have matured, started <a href=\"http:\/\/www.sqlhammer.com\/\">SQLHammer.com<\/a> as one small way to give back and continue the cycle of shared learning.<\/p>\n<p>Derik is the owner and lead author of SQL Hammer, a <a href=\"http:\/\/www.sqlhammer.com\/\">Microsoft SQL Server resource<\/a>.<\/p>\n<p>For more information, visit <a href=\"http:\/\/www.sqlhammer.com\/\">http:\/\/www.sqlhammer.com<\/a>. Follow Derik on <a href=\"http:\/\/twitter.com\/SQLHammer\">Twitter<\/a> for <strong>SQL tips and chat<\/strong><\/p>\n<hr \/>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/hammer-derik-172x140.png\" alt=\"\" \/><strong>Written by Derik Hammer of <a href=\"http:\/\/www.sqlhammer.com\/\">SQL Hammer<\/a><\/strong><\/p>\n<p>Derik is a data professional focusing on Microsoft SQL Server. His passion focuses around <a href=\"http:\/\/www.sqlhammer.com\/blog\/category\/high-availability\/\">high-availability<\/a>, <a href=\"http:\/\/www.sqlhammer.com\/blog\/category\/general\/disaster-recovery\/\">disaster recovery<\/a>, continuous integration, and automated maintenance. his experience has spanned long-term database administration, consulting, and\u00a0entrepreneurial ventures.<\/p>\n<p>Derik gives the SQL community credit for plugging the gaps in his knowledge when he was a junior DBA and, now that his skills have matured, started <a href=\"http:\/\/www.sqlhammer.com\/\">SQLHammer.com<\/a> as one small way to give back and continue the cycle of shared learning.<\/p>\n<p>Derik is the owner and lead author of SQL Hammer, a <a href=\"http:\/\/www.sqlhammer.com\/\">Microsoft SQL Server resource<\/a>.<\/p>\n<p>For more information, visit <a href=\"http:\/\/www.sqlhammer.com\/\">http:\/\/www.sqlhammer.com<\/a>. Follow Derik on <a href=\"http:\/\/twitter.com\/SQLHammer\">Twitter<\/a> for <strong>SQL tips and chat<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Today a co-worker of mine (Brad) asked a question regarding the usage of T-SQL\u2019s Try-Catch block. Brad wanted to write to a table every time a stored procedure failed so that it was easy to recover the error messages and track issues. His Try-Catch block looked something like this. BEGIN TRY BEGIN TRANSACTION PRINT &#8216;do [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":571,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-99","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/sqlhammer.com\/index.php\/wp-json\/wp\/v2\/posts\/99","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sqlhammer.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sqlhammer.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sqlhammer.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/sqlhammer.com\/index.php\/wp-json\/wp\/v2\/comments?post=99"}],"version-history":[{"count":1,"href":"https:\/\/sqlhammer.com\/index.php\/wp-json\/wp\/v2\/posts\/99\/revisions"}],"predecessor-version":[{"id":361,"href":"https:\/\/sqlhammer.com\/index.php\/wp-json\/wp\/v2\/posts\/99\/revisions\/361"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sqlhammer.com\/index.php\/wp-json\/wp\/v2\/media\/571"}],"wp:attachment":[{"href":"https:\/\/sqlhammer.com\/index.php\/wp-json\/wp\/v2\/media?parent=99"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sqlhammer.com\/index.php\/wp-json\/wp\/v2\/categories?post=99"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sqlhammer.com\/index.php\/wp-json\/wp\/v2\/tags?post=99"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}