简体   繁体   中英

T-SQL parameter sniffing recompile plan

I have the SQL command

exec sp_executesql N'SELECT TOP (10) * FROM mytableView WHERE ([Name]) LIKE (''%'' + 
  (@Value0) + ''%'') ORDER BY [Id] DESC',N'@Value0 varchar(5)',@Value0='value'

this sql command execute near 22 seconds. I fount that it happens because I have a parameter sniffing.. If add to end of SQL command option(recompile) it's work fast: 0 seconds was shown in Managements studio

exec sp_executesql N'SELECT TOP (10) * FROM mytableView WHERE ([Name]) LIKE 
  (''%'' + (@Value0) + ''%'') ORDER BY [Id] DESC 
    option(recompile)',N'@Value0 varchar(5)',@Value0='value'

Is it possible to recompile/recreate/erase/update execution plan for my SQL command to work without option(recompile)?

I have tried to apply

  • UPDATE STATISTICS
  • sp_recompile
  • DBCC FREEPROCCACHE
  • DBCC UPDATEUSAGE (0)
  • DBCC FREESYSTEMCACHE ('ALL')
  • ALTER INDEX and REBUILD WITH unfortunately all this actions didn't help me.

You could try the OPTIMIZE FOR UNKNOWN hint instead of RECOMPILE :

exec sp_executesql N'SELECT TOP (10) *
                     FROM mytableView
                     WHERE ([Name]) LIKE (''%'' + (@Value0) + ''%'')
                     ORDER BY [Id] DESC
                     option(OPTIMIZE FOR UNKNOWN);',
                   N'@Value0 varchar(5)',
                   @Value0 = 'value';

The MSDN page for Query Hints states that OPTIMIZE FOR UNKNOWN:

Instructs the query optimizer to use statistical data instead of the initial values for all local variables when the query is compiled and optimized, including parameters created with forced parameterization.

This hint instructs the optimizer to use the total number of rows for the specified table divided by the number of distinct values for the specified column (ie average rows per value) as the row estimate instead of using the statistics of any particular value. As pointed out by @GarethD in a comment below: since this will possibly benefit some queries and possibly hurt others, it needs to be tested to see if the overall gain from this is a net savings over the cost of doing the RECOMPILE. For more details check out: How OPTIMIZE FOR UNKNOWN Works .

And just to have it stated, depending on the distribution of the data and what values are passed in, if there is a particular value being used that has a distribution that is fairly representative of most of the values that could be passed in (even if wildly different from some values that won't ever be passed in), then you can target that value by using OPTIMIZE FOR (@Value0 = 'representative value') rather than OPTIMIZE FOR UNKNOWN .

Please note that this query hint is only needed for queries that have:

  • parameters supplied by variables
  • the field(s) in question do not have a fairly even distribution of values (and hence different values passed in via the variable could generate different plans)

The following scenarios were identified in comments below and do not all require this hint, so here is how to address each situation:

  • select top 80 * from table order by id desc
    There is no variable being passed in here so no query hint needed.

  • select top 80 * from table where id < @lastid order by id desc
    There is a variable being passed in here, but the [id] field, by its very nature, is evenly distributed, even if sparse due to some deletes, hence no query hint needed (or at least should not be needed).

  • SELECT TOP (10) * FROM mytableView WHERE ([Name]) LIKE (''%'' + (@Value0) + ''%'') ORDER BY [Id] DESC
    There is a variable being passed in here, and used in such a way that there could be no indication of consistent numbers of matching rows for different values, especially due to not being able to use an index as a result of the leading % . THIS is a good opportunity for the OPTION (OPTIMIZE FOR UNKNOWN) hint as discussed above.

  • If there is a situation where a variable is passed in that has a greatly varying distribution of matching rows, but not many possible values to get passed in, and the values that are passed in are re-used frequently, then those can be concatenated (after doing a REPLACE(@var, '''', '''''') on it) directly into the Dynamic SQL. This allows for each of those values to have their own separate yet reusable query plan. Other variables should be sent in as parameters as usual.

    For example, a lookup value for [StatusID] will only have a few possible values and they will get reused frequently but each particular value can match a vastly different number of rows. In that case, something like the following will allow for separate execution plans that do not need either the RECOMPILE or OPTIMIZE FOR UNKNOWN hints as each execution plan will be optimized for that particular value:

     IF (TRY_CONVERT(INT, @StatusID) IS NULL) BEGIN ;THROW 50505, '@StatusID was not a valid INT', 55; END; DECLARE @SQL NVARCHAR(MAX); SET @SQL = N'SELECT TOP (10) * FROM myTableView WHERE [StatusID] = ' + REPLACE(@StatusID, N'''', N'''''') -- really only needed for strings + N' AND [OtherField] = @OtherFieldVal;'; EXEC sp_executesql @SQL, N'@OtherFieldVal VARCHAR(50)', @OtherFieldVal = @OtherField; 

    Assuming two different values of @StatusID are passed in (eg 1 and 2 ), there will be two execution plans cached matching the following queries:

    • SELECT TOP (10) * FROM myTableView WHERE [StatusID] = 1 AND [OtherField] = @OtherFieldVal;

    • SELECT TOP (10) * FROM myTableView WHERE [StatusID] = 2 AND [OtherField] = @OtherFieldVal;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM