Date post: | 04-Jun-2015 |
Category: |
Technology |
Upload: | niels-berglund |
View: | 1,010 times |
Download: | 0 times |
SQL Real World Error Handling
Weird SQL Server Error Handling Behavior
Bio - Niels Berglund
Software Specialist - Derivco Software Specialist - lots of production dev. plus figuring out
ways to "use and abuse" existing and new technologies
Author - "First Look at SQL Server 2005 for Developers" Researcher / Instructor - DevelopMentor Speaker - TechEd, DevWeek, SQL Pass, etc. Longtime user of SQL Server
www.derivco.com [email protected] @nielsberglund
9/7/2013 | Footer Goes Here2 |
Derivco
World's leading development house for online gaming software; Casino, Poker, Bingo etc.
Technology company One of the world's largest install base of SQL Server's
(~300 SQL Server servers, multiple instances & db's). SQL Server 2008 / 2012, research into 2014 Hadoop, Windows Azure .NET 4.5, SignalR, WebSockets, mobile (Android, iOS)
Offices in Durban, Cape Town, Pretoria (soon) Estonia, Hong Kong, Sweden, UK
WE ARE HIRING!!
Background
Once upon a time … Pre SQL 2005, arcane error handling Checking @@ERROR after each statement
execution does not halt at an error
SELECT 1 / 0 AS ResultSET @err = @@ERRORIF(@err <> 0)BEGIN --handle the errorEND
Background II (a.k.a Ivory Tower)
SQL 2005 introduced the concept of TRY … CATCH Instead of checking @@ERROR for every statement,
the code is enveloped in a TRY block, followed by a CATCH block.
It is neat, simple and all code should use this method or should it???
BEGIN TRY SET @x = 1 / 0 PRINT 'Hello World';END TRYBEGIN CATCH SET @errMsg = ERROR_MESSAGE(); --code to handle the errorEND CATCH
Real world
Derivco: ~300 SQL Server Servers, multiple instances. Main production db.; ~1,500 tables, ~5,000 procedures
In a live environment, you cannot change all code to use the new features!
New code; yes most definitely - you have full control of the call chain.
Editing existing procedures; be careful, be very careful there are subtle and not so subtle differences when
using try…catch blocks.
RETURN and @@ERROR
A proc always returns an integer as return value.
The return value can be implicit, or set explicitly by the RETURN xxx statement.
If a proc encounters an error and no return value is defined, it returns -6 (undocumented…)
TRY blocks
Code executing inside a TRY block executes in a specific error related context.
When an error occurs you are immediately transferred to the CATCH block.
Downstream, if no TRY CATCH block exist - you end up in the calling code's CATCH block what if you handle errors the "old" way
downstream? that will not happen, ouch!
CATCH blocks
The error happening in the TRY block is handled in the CATCH block.
Even if an error occurs and is handled in a CATCH, the default return value of the proc indicates a failure (-6). if an explicit return value has been set, that value
will be returned.
To (re - ) raise an error in a CATCH block, can have strange side effects.
CATCH and RAISERROR
You can (re)raise errors in a CATCH block Those errors will be caught in calling procs TRY … CATCH blocks. However, in order for "old"-style error handling to catch those errors,
there MUST be a RETURN immediately following the RAISERROR not RETURN variable but RETURN
BEGIN CATCH SET @errMsg = ERROR_MESSAGE(); PRINT 'We are in the catch block in the error proc' --re-raise the error, this will not make @@ERROR to catch the error upstrram RAISERROR('Something went wrong: %s', 16, -1, @errMsg);END CATCH
BEGIN CATCH SET @errMsg = ERROR_MESSAGE(); PRINT 'We are in the catch block in the error proc' --re-raise the error, this will make @@ERROR to catch the error upstrram RAISERROR('Something went wrong: %s', 16, -1, @errMsg); RETURNEND CATCH
THROW
Pre SQL 2012 no easy way to re-throw an error you captured the error message and then did a RAISERROR
with the message. SQL 2012 introduces THROW THROW can be used in-place of RAISERROR
it does not support substitution parameters though you can however raise a specific error without that error number
existing in sys.messages The THROW statement has to be used in conjunction with
TRY CATCH. If no TRY CATCH is available for a THROW statement, the session is ended!
In a CATCH block THROW can be used to re-throw an error
THROW examples
--syntaxTHROW [ { error_number | @local_variable }, { message | @local_variable }, { state | @local_variable } ] [ ; ]
--do some stuffTHROW 52000, 'Something weird happened', 1
BEGIN CATCH SET @errMsg = ERROR_MESSAGE(); PRINT 'We are in the catch block in the error proc'; --tis requires the calling proc, to have a try catch block THROW;END CATCH
Summary
TRY CATCH blocks ARE good! However, be carful when mixing new TRY CATCH with
"old" @@ERROR You need to check all nested procedures called inside the
Try block and: Make sure your error handling is correct (to the n level) Potentially handle Transactions started in nested
procedures If raising an error in a CATCH block, ALWAYS follow the
RAISERROR with a RETURN (no value). Unless you can guarantee that your code will always use
TRY CATCH, stay away from THROW.