THE SQL Server Blog Spot on the Web

Welcome to SQLblog.com - The SQL Server blog spot on the web Sign in | Join | Help
in Search

Aaron Bertrand

Aaron is a senior consultant for SQL Sentry, Inc., makers of performance monitoring and event management software for SQL Server, Analysis Services, and Windows. He has been blogging here at sqlblog.com since 2006, focusing on manageability, performance, and new features; has been a Microsoft MVP since 1997; tweets as @AaronBertrand; and speaks frequently at user group meetings and SQL Saturday events.

Bad Habits to Kick : Using EXEC() instead of sp_executesql

Today in my T-SQL: Bad Habits to Kick session at SQL Saturday #84 in Kalamazoo, a user asked if SQL Server supported anything like bind variables in Oracle when using dynamic SQL.

When using dynamic SQL, you have two choices: EXEC() / EXECUTE(), or sp_executesql. Early on my career, I used EXEC() a lot, because it was much easier. I've since learned that sp_executesql is a lot better, for two main reasons:

  1. You substantially reduce the risk of SQL injection by continuing to pass strongly-typed variables throughout the whole chain. Which do you find more questionable?
    DECLARE @foo NVARCHAR(32) = N'string''; DROP TABLE dbo.table; --''';

    DECLARE @sql NVARCHAR(MAX) = N'SELECT foo FROM dbo.table WHERE foo = ''' + @foo + '''';
    EXEC(@sql);
    ... or ...
    DECLARE @foo NVARCHAR(32) = N'string''; DROP TABLE dbo.table; --''';

    DECLARE @sql NVARCHAR(MAX) = N'SELECT foo FROM dbo.table WHERE foo = @foo;';
    EXEC sp_executesql @sql, N'@foo NVARCHAR(32)', @foo;
    In the latter case, @foo is a parameter all the way through, making it quite difficult to expose yourself to SQL injection. The results will literally look for the following value:
    string'; DROP TABLE dbo.table; --'
    Which will return an empty result instead of trying to execute two commands on the server.

  2. It is much more likely that a plan will get re-used, even for dynamic SQL, when using sp_executesql. This is because the statement itself always looks the same to the optimizer. Now, can you get bitten by parameter sniffing still? Of course. But you can't solve that by switching between EXEC and sp_executesql; it is something you'll need to use OPTIMIZE FOR or RECOMPILE query hints, or maybe even the optimize for ad hoc workloads setting. This behavior is quite similar to how bind variables in Oracle work.

There are going to be cases where your parameters can't simply be passed in directly, such as variable table names. The threat for SQL injection still exists there, but the performance issues are not really relevant - if the query is going to be pulling from a different table potentially each time, you should definitely be using RECOMPILE.

The bottom line is this: query plan reuse is one of your very best friends; concatenation is the long-lost acquaintance from high school that just might steal your lunch.

For some more background on EXEC vs. sp_executesql, see this MSDN topic and also Denis Gobo's post reminding us that simply changing EXEC to sp_executesql is not a silver bullet.

 

Published Saturday, September 17, 2011 1:50 PM by AaronBertrand

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

giantism_strikes said:

Since this is doing a soft parse and the query plan will be reused, is there a performance gain between using a stored procedure and dynamic, parameterized SQL?

September 17, 2011 2:56 PM
 

AaronBertrand said:

I don't believe so, but I would still opt for stored procedures for other reasons (manageability, maintenance, modularization, consistency).

September 17, 2011 4:48 PM
 

jchang said:

my preference is to employ stored procedures for the reason AB cited

and then within a stored procedure, employ sp_executesql to handle multiple optional search arguments

example:

instead of this code

SELECT xx

FROM TableA JOIN TableB etc

WHERE (@P1 IS NULL OR Col1 = @P1)

 AND (@P2 IS NULL OR Col2 = @P2)

etc

use sp_executesql instead

DECLARE @SQL nvarchar(xxx), @b tinyint = 0

SET @SQL = N'SELECT xx FROM TableA JOIN TableB etc WHERE 1=1 '

IF @P1 IS NOT NULL

SET @SQL = @SQL + N' AND Col1 = @P1 '

IF @P2 IS NOT NULL

SET @SQL = @SQL + N' AND Col2 = @P2 '

I would just declare and apply all the parameters, regardless if they are null

as in

exec sp_executesql @SQL, N'@P1 type, @P2 type, etc', @P1, @P2, etc

September 18, 2011 12:22 PM
 

Jack Corbett said:

I agree with Aaron and Joe about still using SP's and the method Joe espouses for multiple optional search arguments.  It can get a bit ugly looking with a lot of parameters, but is still easier to maintain and expand.  I have blogged about it as well.

September 23, 2011 10:40 AM
 

SQL Injection, Part 7 Of 8: Defending Dynamic Stored Procedures « 36 Chambers – The Legendary Journeys: Execution to the max! said:

November 8, 2011 1:18 PM

Leave a Comment

(required) 
(optional)
(required) 
Submit

About AaronBertrand

...about me...

This Blog

Syndication

Powered by Community Server (Commercial Edition), by Telligent Systems
  Privacy Statement