Impact of variables in a paramatized SQL query

Viewed 145

I have a parametrized query which looks like (With ? being the applications parameter):

SELECT * FROM tbl WHERE tbl_id = ?

What are the performance implications of adding a variable like so:

DECLARE @id INT = ?;
SELECT * FROM tbl WHERE tbl_id = @id

I have attempted to investigate myself but have had no luck other than query plans taking slightly longer to compile when the query is first run.

3 Answers

If tbl_id is unique there is no difference at all. I'm trying to explain why.

SQL Server usually can solve a query with many different execution plans. SQL Server has to choose one. It tries to find the the most efficient one without too much effort. Once SQL Server chooses one plan it usually caches it for later reuse. Cardinality plays a key role in the efficiency of an execution plan, i.e How many rows there are on tbl with a given value of tbl_id?. SQL Server stores column values frequency statistics to estimate cardinality.

Firstly, lets assume tbl_id is not unique and has a non uniform distribution.

In the first case we have tbl_id = ?. Lets figure out its cardinality. The first thing we need to do to figure it out is knowing the value of the parameter ?. Is it unknown? Not really. We have a value the first time the query is executed. SQL Server takes this value, it goes to stored statistics and estimates cadinality for this specific value, it estimates the cost of a bunch of possible execution plans taking into account the estimated cardinality, chooses the most efficient one and cache it for later reuse. This approach works most of the time. However if you execute the query later with other parameter value that has a very different cardinality, the cached execution plan might be very inefficient.

In the second case we have tbl_id = @id being @id a variable declared in the query, it isn't a query parameter. Which is the value of @id?. SQL Server treats it as an unknown value. SQL Server peaks the mean frequency from stored statistics as the estimated cardinality for unknown values. Then SQL Server do the same as before: it estimates the cost of a bunch of possible execution plans taking into account the estimated cardinality, chooses the most efficient one and cache it for later reuse. Again, this approach works most of the time. However if you execute the query with one parameter value that has a very different cardinality than the mean, the execution plan might be very inefficient.

When all values have the same cardinality they have the mean cardinality, so there is no difference between parameter and variable. This is the case of unique values, therefore there are no difference when values are unique.

one advantage of the 2nd approach is that is reduces the number of plans SQL will store. in the first version it will create a different plan for every datatype (tinyint, smallint,int & bigint)

thats assuming its an adhoc statement.

If its in a stored proc - you might run into p-sniffing as mentioned above.

You could try adding

OPTION ( OPTIMIZE FOR (@id = [some good value]))

to the select to see if that helps - but is is usually not considered good practice to couple your queries to values.

I'm not sure if this helps, but I have to account for parameter sniffing for a lot of the stored procedures I write. I do this by creating local variables, setting those to the parameter values, and then using the local variables in the stored procedure. If you look at the stored execution plan, you can see that this prevents the parameter values from being used in the plan.

This is what I do:

CREATE PROCEDURE dbo.Test ( @var int )
AS
DECLARE 
    @_var int
SELECT 
    @_var = @var

SELECT * 
FROM dbo.SomeTable 
WHERE
    Id = @_var

I do this mostly for SSRS. I've had a query/stored procedure return <1sec, but the report takes several minutes, for example. Doing the trick above fixed that.

There are also options for optimizing specific values (e.g. OPTION (OPTIMIZE @var FOR UNKNOWN)), but I've found this usually does not help me and will not have the same effects as the trick above. I haven't been able to investigate the specifics into why they are different, but I have experienced the OPTIMIZE FOR UNKNOWN did not help, where as using local variables in place of variables did.

Related