Excelgoodies logo +31 97 010285556

LEARN THIS HANDS ON

Power BI with SQL

. Live Online FILLING FAST
View all upcoming batches
Parameter Sniffing in MS-SQL 2026: Detection and the Right Fix for Each Scenario

Parameter Sniffing in MS-SQL 2026: Detection and the Right Fix for Each Scenario

Parameter sniffing is still one of the most common causes of unstable query performance in MS-SQL 2026. In this article you'll see how to detect real parameter sniffing problems, then choose the right fix: RECOMPILE, OPTIMIZE FOR, or plan guides.

If you’re using SQL as the backbone for Power BI models or other reporting tools, understanding parameter sniffing is key to keeping refreshes predictable; it pairs nicely with building robust SQL-based models using something like a Power BI with SQL deep-dive.


What Parameter Sniffing Actually Is (and Isn’t)

When a parameterized query or stored procedure runs the first time, SQL Server uses the actual parameter values to estimate row counts, choose indexes, and build an execution plan. That plan is cached and reused for later executions.

Parameter sniffing problems occur when:

  • The first execution uses an unusual parameter value (very selective or very unselective).
  • SQL Server builds a plan that is good for that value, but bad for most other values.
  • The plan is reused for all subsequent executions, causing big swings in runtime.

This is not always bad. Most of the time, parameter sniffing is beneficial. You only need to act when:

  • The same query sometimes runs in milliseconds, sometimes in minutes.
  • The slow runs correlate with specific parameter values.

Step 1: Detecting Parameter Sniffing in the Wild

1. Look for Parameter-Sensitive Queries in Query Store

If Query Store is enabled (it should be on any serious 2026 instance), it’s your best starting point.

  1. Identify top resource-consuming queries:
    SELECT TOP 50
        qsq.query_id,
        qsqt.query_sql_text,
        SUM(rs.avg_duration * rs.count_executions) AS total_duration,
        COUNT(DISTINCT rs.plan_id) AS plan_count
    FROM sys.query_store_query qsq
    JOIN sys.query_store_query_text qsqt
        ON qsq.query_text_id = qsqt.query_text_id
    JOIN sys.query_store_plan qsp
        ON qsq.query_id = qsp.query_id
    JOIN sys.query_store_runtime_stats rs
        ON qsp.plan_id = rs.plan_id
    GROUP BY qsq.query_id, qsqt.query_sql_text
    ORDER BY total_duration DESC;
    
  2. Filter to queries with multiple plans (plan_count > 1) and large variation in avg_duration between plans.
  3. For a suspicious query_id, drill into runtime stats by parameter values.

Parameter sniffing suspects usually show:

  • Several plans for the same query.
  • Some plans with very low average duration, others extremely high.

2. Compare Actual Execution Plans for Different Parameter Values

Run the same stored procedure or query twice with different parameter values and capture actual execution plans.

Example:

-- Example proc
CREATE OR ALTER PROC dbo.GetOrders
    @CustomerId INT
AS
BEGIN
    SELECT o.OrderID, o.OrderDate, o.Amount
    FROM dbo.Orders o
    WHERE o.CustomerId = @CustomerId;
END;
GO

-- Execution 1: rare customer
EXEC dbo.GetOrders @CustomerId = 999999; -- very few rows

-- Execution 2: heavy customer
EXEC dbo.GetOrders @CustomerId = 42; -- many rows

Symptoms of parameter sniffing:

  • Plan for rare value uses index seek + key lookup, but is reused for heavy value (causing thousands of lookups).
  • Or plan for heavy value uses scan + hash join, but is reused for rare value (overkill work for tiny result sets).

3. Watch for Big Cardinality Estimate Errors

In the execution plan, compare:

  • Estimated Number of Rows vs Actual Number of Rows.

If estimates are consistently off by orders of magnitude only for certain parameter values, that’s a strong sign.


Step 2: Understand Your Options

Once you confirm parameter sniffing, you have three main knobs:

  1. RECOMPILE (per query or per execution)
  2. OPTIMIZE FOR hints
  3. Plan guides

Each has a specific sweet spot and trade-offs.


Option 1: RECOMPILE – When You Want Fresh Plans Every Time

How It Works

RECOMPILE forces SQL Server to build a new plan for each execution of the statement or procedure, using the current parameter values.

You can apply it:

  • At statement level:

    SELECT o.OrderID, o.OrderDate, o.Amount
    FROM dbo.Orders o
    WHERE o.CustomerId = @CustomerId
    OPTION (RECOMPILE);
    
  • At procedure level:

    CREATE OR ALTER PROC dbo.GetOrders
        @CustomerId INT
    WITH RECOMPILE
    AS
    BEGIN
        SELECT o.OrderID, o.OrderDate, o.Amount
        FROM dbo.Orders o
        WHERE o.CustomerId = @CustomerId;
    END;
    

Pros

  • Always uses parameter-specific estimates and plan.
  • No bad plan reuse, so very predictable performance.
  • Easy to implement and revert.

Cons

  • Extra CPU for compilation on every execution.
  • Plan cache benefits are lost for that statement/proc.

When to Use RECOMPILE

Use RECOMPILE when:

  • The query is called infrequently, but is expensive when it runs.
  • The parameter distribution is extremely skewed and you need a custom plan per execution.
  • You’re troubleshooting a critical performance issue and need a quick, reversible fix.

Avoid it on:

  • Very high-frequency OLTP queries where compile CPU could become a bottleneck.

Option 2: OPTIMIZE FOR – When You Need a Stable, Representative Plan

OPTIMIZE FOR tells the optimizer which parameter value to use when estimating, regardless of the actual runtime value.

Basic Patterns

  1. Optimize for a specific value:

    SELECT o.OrderID, o.OrderDate, o.Amount
    FROM dbo.Orders o
    WHERE o.CustomerId = @CustomerId
    OPTION (OPTIMIZE FOR (@CustomerId = 42));
    
  2. Optimize for UNKNOWN (use average density instead of sniffed value):

    SELECT o.OrderID, o.OrderDate, o.Amount
    FROM dbo.Orders o
    WHERE o.CustomerId = @CustomerId
    OPTION (OPTIMIZE FOR UNKNOWN);
    

You can use these hints inside stored procedures by adding them to individual statements.

Pros

  • One stable plan, no surprises from first-execution values.
  • Keeps plan cache benefits.
  • Less CPU than RECOMPILE in high-frequency workloads.

Cons

  • You’re locking in one plan for all values, so it must be “good enough” for most cases.
  • Choosing the wrong value to optimize for can make things worse.

When to Use OPTIMIZE FOR

Use OPTIMIZE FOR when:

  • You have two or more distinct parameter patterns, but one dominates.
  • You understand typical data distributions and can pick a representative value.
  • You want to stabilize performance for reporting workloads.

OPTIMIZE FOR UNKNOWN is handy when:

  • Data distribution is skewed, but you can’t pick a single representative value.
  • The default sniffed plan is good for extremes but bad for the middle.

Option 3: Plan Guides – When You Can’t Touch the Code

Plan guides let you attach hints (like OPTIMIZE FOR or RECOMPILE) to queries without changing the source. Useful for:

  • Vendor applications where you can’t edit stored procedures.
  • Legacy code where you want to avoid risky deployments.

Example: Forcing OPTIMIZE FOR via Plan Guide

Assume the vendor proc runs this query (simplified):

SELECT o.OrderID, o.OrderDate, o.Amount
FROM dbo.Orders o
WHERE o.CustomerId = @CustomerId;

You can attach a plan guide with OPTIMIZE FOR UNKNOWN:

EXEC sp_create_plan_guide
    @name = N'Guide_Orders_OptimizeForUnknown',
    @stmt = N'SELECT o.OrderID, o.OrderDate, o.Amount
              FROM dbo.Orders o
              WHERE o.CustomerId = @CustomerId;',
    @type = N'SQL',
    @module_or_batch = NULL,
    @params = N'@CustomerId INT',
    @hints = N'OPTION (OPTIMIZE FOR UNKNOWN)';

Key points:

  • @stmt must match the query text exactly as SQL Server sees it (whitespace and formatting matter).
  • Use sys.fn_get_sql or Query Store to copy the exact text when possible.

Pros

  • No code changes in the application or procedure.
  • Can be rolled back centrally.

Cons

  • Maintenance overhead; plan guides can be brittle when code changes.
  • Harder to document and discover for new team members.

When to Use Plan Guides

Use plan guides when:

  • You can’t modify the query or proc.
  • You need to force a specific hint (OPTIMIZE FOR, RECOMPILE, or even index hints) for a known problematic query.

Prefer code changes over plan guides whenever you can change the code.


Choosing Between RECOMPILE, OPTIMIZE FOR, and Plan Guides

When you’ve confirmed parameter sniffing, walk through these decision points.

1. Can You Change the Code?

  • Yes → Prefer direct hints in the query or proc.
  • No → Use a plan guide.

2. How Frequently Is the Query Executed?

  • Low frequency, high cost:
    • OPTION (RECOMPILE) is usually safe and simple.
  • High frequency, moderate cost:
    • Avoid full RECOMPILE unless CPU is abundant.
    • Try OPTIMIZE FOR or OPTIMIZE FOR UNKNOWN.

3. How Skewed Is the Data?

  • Highly skewed, and different plans are really needed for different ranges:

    • If you can tolerate compile cost → RECOMPILE.
    • If not, consider splitting logic:
      IF @CustomerId < 1000
      BEGIN
          -- Path 1: small customers
          SELECT ...
          OPTION (OPTIMIZE FOR (@CustomerId = 10));
      END
      ELSE
      BEGIN
          -- Path 2: big customers
          SELECT ...
          OPTION (OPTIMIZE FOR (@CustomerId = 50000));
      END
      
  • Moderately skewed:

    • Use OPTIMIZE FOR with a carefully chosen typical value.
    • Or OPTIMIZE FOR UNKNOWN if you want a generic plan.

4. Do You Need a Quick, Reversible Fix?

  • First response to a production incident:
    • Apply OPTION (RECOMPILE) or a temporary plan guide.
    • Once stable, revisit and refine with OPTIMIZE FOR or code changes.

Practical Workflow for Handling Parameter Sniffing

You can turn this into a repeatable playbook:

  1. Identify the query

    • Use Query Store to find high-variance queries (duration, CPU, reads).
  2. Reproduce with different parameters

    • Run the query/proc with a “fast” and a “slow” parameter.
    • Capture actual execution plans.
  3. Confirm sniffing

    • Check if the slow execution is reusing a plan built for a very different parameter.
    • Look at cardinality estimate vs actual rows.
  4. Pick the least invasive fix

    • Code change with OPTIMIZE FOR or RECOMPILE.
    • If code is locked, use a plan guide.
  5. Monitor after the change

    • Watch Query Store for improved stability.
    • Validate that you didn’t just move the problem to another parameter range.

One Takeaway: Treat Parameter Sniffing as a Targeted Fix, Not a Toggle

Don’t try to “turn off” parameter sniffing globally; it’s usually helping you. Use Query Store to find the handful of queries where sniffing hurts, then choose the smallest, most targeted fix: RECOMPILE for rare, heavy hitters, OPTIMIZE FOR for stable workhorses, and plan guides only when you can’t touch the code.

MS-SQL

New

Next Batches Now Live

Power BIPower BI
SQLSQL
Power AppsPower Apps
Power AutomatePower Automate
Microsoft FabricMicrosoft Fabrics
AzureAzure Data Engineering
Explore Dates & Reserve Your Spot → Reserve Your Spot →