Have you ever seen a SQL Server query run quickly in one situation but become very slow when the application passes a different parameter? The query has not changed, the database is still running, and yet the response time is completely different.
One possible reason is parameter sniffing.
SQL Server can use the parameter values supplied during compilation to choose an execution plan. That plan may work well for some values but perform poorly for others. When SQL Server reuses the cached plan, a query that was previously fast can become slow for a different parameter value.
Parameter sniffing is not always a problem. In fact, it often helps SQL Server choose efficient execution plans. The problem occurs when the selected plan does not perform well for the range of values the application uses.
In this article, we will understand why parameter sniffing happens, how to identify it, and which solutions are worth considering before changing your queries.
What Is Parameter Sniffing in SQL Server?
Suppose you have an Orders table containing millions of records. Most customers have only a few orders, but a small number of customers have thousands.
Your application runs this query:
CREATE OR ALTER PROCEDURE dbo.GetCustomerOrders
@CustomerId INT
AS
BEGIN
SELECT
OrderId,
CustomerId,
OrderDate,
TotalAmount
FROM dbo.Orders
WHERE CustomerId = @CustomerId;
END;When SQL Server compiles the stored procedure, it can use the supplied @CustomerId value to estimate how many rows the query will return.
Based on that estimate, SQL Server chooses an execution plan. It may use an index seek with lookups, an index scan, or another strategy depending on the available indexes, statistics, and estimated costs.
The selected plan can then be cached and reused for later executions.
Imagine the first execution uses a customer with only three orders. SQL Server chooses a plan that works well for a small result set. Later, the application requests a customer with 50,000 orders. The same plan might now perform poorly.
This is one example of parameter sniffing.
The reverse can also happen. A plan compiled for a customer with many orders might be inefficient for customers with only a few records.
The important point is that the best execution plan can depend on the parameter value.
Why Parameter Sniffing Happens
Several factors can contribute to parameter-sensitive query performance.
1. Data Is Not Distributed Evenly
Some customers may have very few orders, while others have many. A query that returns ten rows for one parameter might return thousands for another.
When SQL Server estimates the number of rows incorrectly for a later execution, the cached plan may no longer be a good choice.
2. SQL Server Reuses Execution Plans
Compiling a query has a cost, so SQL Server can reuse execution plans from its plan cache.
Plan reuse is usually beneficial. However, a cached plan is not necessarily optimal for every possible parameter value.
3. Index Choices Depend on Estimated Rows
An index seek may work well when a query returns a small number of rows. A scan may be more efficient when a large portion of a table is required.
The optimizer considers several factors when choosing a plan. If the estimates are unsuitable for the current parameter, the chosen access method or join strategy may also be unsuitable.
4. Statistics May Not Represent the Data Well
SQL Server uses statistics to estimate row counts and selectivity.
Outdated statistics, uneven data distributions, and complex predicates can affect these estimates. However, updating statistics does not automatically solve every parameter sniffing problem.
How to Identify Parameter Sniffing
Before changing the query, confirm that parameter-sensitive plan reuse is actually causing the problem.
Step 1: Compare Execution Times for Different Parameters
Run the stored procedure with parameter values that return different numbers of rows.
EXEC dbo.GetCustomerOrders @CustomerId = 10;
EXEC dbo.GetCustomerOrders @CustomerId = 5000;These values are illustrative. Use real parameter values from your application or test data.
Compare execution time, logical reads, CPU usage, and the number of rows returned.
If performance changes significantly between parameter values, investigate further. Different execution times alone do not prove parameter sniffing because blocking, storage latency, concurrent workloads, and other factors can produce similar symptoms.
Step 2: Review the Actual Execution Plan
In SQL Server Management Studio, enable the actual execution plan and run the query.
Look for differences between estimated and actual row counts. For example, an operator might estimate that a query returns a few rows but actually process thousands.
Pay attention to:
Index seeks and scans.
Key lookups.
Join algorithms.
Memory grants and spills.
Large differences between estimated and actual rows.
A large estimation error is a useful clue, but it does not independently prove that parameter sniffing is responsible.
Step 3: Check Query Store
Query Store records query performance information and execution plans when it is enabled and configured appropriately.
It can help you compare plans, identify performance changes, and determine whether a query became slower after a different plan was introduced.
For example, you can inspect query statistics with a query like this:
SELECT TOP (20)
q.query_id,
p.plan_id,
rs.count_executions,
rs.avg_duration,
rs.avg_cpu_time,
rs.avg_logical_io_reads
FROM sys.query_store_query AS q
INNER JOIN sys.query_store_plan AS p
ON q.query_id = p.query_id
INNER JOIN sys.query_store_runtime_stats AS rs
ON p.plan_id = rs.plan_id
ORDER BY rs.avg_duration DESC;This example provides a starting point for investigation. Query Store runtime statistics are stored across intervals, so a more detailed analysis should account for interval boundaries, execution counts, and the relevant time range.
If the same query has multiple plans with noticeably different performance, investigate why those plans were selected and which parameter values were involved.
How to Fix Parameter Sniffing
There is no single fix that works for every query. The right solution depends on how much parameter values affect the execution plan and how frequently the query runs.
Solution 1: Use OPTION (RECOMPILE)
SQL Server can compile a statement again for each execution using OPTION (RECOMPILE).
CREATE OR ALTER PROCEDURE dbo.GetCustomerOrders
@CustomerId INT
AS
BEGIN
SELECT
OrderId,
CustomerId,
OrderDate,
TotalAmount
FROM dbo.Orders
WHERE CustomerId = @CustomerId
OPTION (RECOMPILE);
END;
Join the conversation! Your thoughts help the community grow.