How to Remove a Specific Bad Execution Plan from SQL Server Cache Without Flushing the Entire Plan Cache


2 views

We've all encountered this scenario - a normally fast-running query suddenly starts performing poorly because SQL Server cached a suboptimal execution plan. In my production environment, I have a parameterized query that executes every 5 minutes. 99% of the time it runs in milliseconds, but occasionally it gets stuck with a plan that takes 30+ seconds to execute.

First, we need to find the plan handle for the problematic query:

SELECT qs.plan_handle, qs.query_hash, qt.text, qs.execution_count,
       qs.total_logical_reads/qs.execution_count AS avg_logical_reads,
       qs.total_elapsed_time/qs.execution_count AS avg_elapsed_time
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
WHERE qt.text LIKE '%YourQueryText%' 
ORDER BY avg_elapsed_time DESC;

Once you've identified the bad plan, here are three precise ways to remove it:

Method 1: Using DBCC FREEPROCCACHE

-- Remove specific plan using plan handle
DBCC FREEPROCCACHE(0x060006001ECA270EC0211D05000000000000000000000000);
-- Replace with your actual plan handle

Method 2: Using Plan Guide

-- Create plan guide to force parameterization
EXEC sp_create_plan_guide 
    @name = N'Fix_Bad_Plan',
    @stmt = N'SELECT * FROM Orders WHERE CustomerID = @CustomerID',
    @type = N'SQL',
    @module_or_batch = NULL,
    @params = N'@CustomerID int',
    @hints = N'OPTION (OPTIMIZE FOR (@CustomerID = 42))';

Method 3: Query Store (SQL Server 2016+)

For newer versions, Query Store provides better control:

-- Force a specific plan
EXEC sp_query_store_force_plan @query_id = 123, @plan_id = 456;

To stop this from happening again:

  • Use OPTION (RECOMPILE) for queries with highly variable parameters
  • Implement proper parameterization to avoid parameter sniffing issues
  • Consider using OPTIMIZE FOR hints for known-good parameter values

Set up regular monitoring to catch plan regressions early:

SELECT TOP 20
    qs.query_hash,
    qt.text,
    qs.execution_count,
    qs.total_elapsed_time/qs.execution_count AS avg_elapsed_time,
    qs.last_elapsed_time/1000 AS last_elapsed_time_ms
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
ORDER BY qs.last_elapsed_time DESC;

In SQL Server 2008, we often encounter situations where a normally fast-running query (executing in milliseconds) suddenly starts performing poorly due to a cached bad execution plan. This typically happens when:

  • The query uses parameters with skewed data distributions
  • Statistics change after the plan is cached
  • Initial parameter values create a suboptimal plan

Here are precise methods to evict just the problematic plan without clearing the entire cache:

-- Method 1: Using plan_handle from sys.dm_exec_cached_plans
DECLARE @plan_handle varbinary(64)
SELECT @plan_handle = plan_handle 
FROM sys.dm_exec_cached_plans 
CROSS APPLY sys.dm_exec_sql_text(plan_handle) 
WHERE text LIKE '%YourProblemQuery%' 
AND text NOT LIKE '%sys.dm_exec_cached_plans%'

IF @plan_handle IS NOT NULL
BEGIN
    DBCC FREEPROCCACHE(@plan_handle)
    PRINT 'Problematic plan removed successfully'
END

For more precise targeting when dealing with similar but distinct queries:

-- Method 2: Using query_hash
DECLARE @query_hash binary(8)
SELECT @query_hash = query_hash 
FROM sys.dm_exec_query_stats 
CROSS APPLY sys.dm_exec_sql_text(sql_handle) 
WHERE text LIKE '%YourProblemQuery%'

IF @query_hash IS NOT NULL
BEGIN
    DBCC FREEPROCCACHE(0x) + CONVERT(varchar(64), @query_hash, 2)
    PRINT 'Plans with matching query hash cleared'
END

To avoid recurrence after removing the bad plan:

-- Option A: Use OPTIMIZE FOR hint
EXEC sp_executesql N'SELECT * FROM Orders 
WHERE OrderDate > @date 
OPTION (OPTIMIZE FOR (@date = ''20200101''))',
N'@date datetime', @date = '20230101'

-- Option B: Force recompile each execution
EXEC sp_executesql N'SELECT * FROM Orders 
WHERE OrderDate > @date 
OPTION (RECOMPILE)',
N'@date datetime', @date = '20230101'

-- Option C: Use plan guide
EXEC sp_create_plan_guide 
@name = N'ForceGoodPlan',
@stmt = N'SELECT * FROM Orders WHERE OrderDate > @date',
@type = N'SQL',
@module_or_batch = NULL,
@params = N'@date datetime',
@hints = N'OPTION (OPTIMIZE FOR (@date = ''20200101''))'

After plan removal, verify with:

-- Check if plan still exists
SELECT cp.usecounts, cp.cacheobjtype, cp.objtype, st.text
FROM sys.dm_exec_cached_plans cp
CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st
WHERE st.text LIKE '%YourProblemQuery%'