Business Professionals
Power BI | Power Pivot | Power Query | DAX
Cloud Flows | RPA | AI Builder | Copilot
60+ Formulas | Data Stories | Advanced Reporting & Modeling
VB Programming | Report Automation |
MS-Office Automation
Techno-Business Professionals
Power BI | Power Query | Advanced DAX | SQL - Query &
Programming
Microsoft Fabric | Power BI | Power Query | Advanced DAX |
SQL - Query & Programming
Power BI | Power Apps | Power Automate | Copilot Studio | Power Pages | Dataverse
Microsoft Power Apps | Microsoft Power Automate
Power BI | Adv. DAX | SQL (Query & Programming) |
VBA | Python | Web Scrapping | API Integration
Power BI | Power Apps | Power Automate |
SQL (Query & Programming)
Power BI | Adv. DAX | Power Apps | Power Automate |
SQL (Query & Programming) | VBA | Python | Web Scrapping | API Integration
Power Apps | Power Automate | SQL | VBA | Python |
Web Scraping | RPA | API Integration
Technology Professionals
Power BI | DAX | SQL | ETL with SSIS | SSAS | VBA | Python
Power BI | SQL | Azure Data Lake | Synapse Analytics |
Data Factory | Databricks | Power Apps | Power Automate |
Azure Analysis Services
Microsoft Fabric | Power BI | SQL | Lakehouse |
Data Factory (Pipelines) | Dataflows Gen2 | KQL | Delta Tables | Power Apps | Power Automate
Power BI | Power Apps | Power Automate | SQL | VBA | Python | API Integration
Power BI | Advanced DAX | Databricks | SQL | Lakehouse Architecture
Business Professionals
Power BI | Power Pivot | Power Query | DAX
Cloud Flows | RPA | AI Builder | Copilot
60+ Formulas | Data Stories | Advanced Reporting & Modeling
VB Programming | Report Automation |
MS-Office Automation
Techno-Business Professionals
Power BI | Power Query | Advanced DAX | SQL - Query &
Programming
Microsoft Fabric | Power BI | Power Query | Advanced DAX |
SQL - Query & Programming
Power BI | Power Apps | Power Automate | Copilot Studio | Power Pages | Dataverse
Microsoft Power Apps | Microsoft Power Automate
Power BI | Adv. DAX | SQL (Query & Programming) |
VBA | Web Scrapping | API Integration
Power BI | Power Apps | Power Automate |
SQL (Query & Programming)
Power BI | Adv. DAX | Power Apps | Power Automate |
SQL (Query & Programming) | VBA | Web Scrapping | API Integration
Power Apps | Power Automate | SQL | VBA |
Web Scraping | RPA | API Integration
Technology Professionals
Power BI | DAX | SQL | ETL with SSIS | SSAS | VBA
Power BI | SQL | Azure Data Lake | Synapse Analytics |
Data Factory | Azure Analysis Services
Microsoft Fabric | Power BI | SQL | Lakehouse |
Data Factory (Pipelines) | Dataflows Gen2 | KQL | Delta Tables
Power BI | Power Apps | Power Automate | SQL | VBA | API Integration
Power BI | Advanced DAX | Databricks | SQL | Lakehouse Architecture
Parameter sniffing in MS-SQL can turn a perfectly fine query into a random performance disaster depending on which parameter value runs first. This article walks through modern mitigation patterns using Query Store and OPTIMIZE FOR hints, with concrete scripts you can apply to real systems, especially useful if your SQL layer feeds Power BI or other reporting tools.
If you’re building a reporting stack where SQL performance directly impacts visuals and refresh times, it’s worth investing in deeper skills around SQL tuning for analytics workloads, for example when combining SQL Server with Power BI in a setup similar to a robust SQL-powered BI stack.
You already know the basics: SQL Server compiles an execution plan for a parameterized query or stored procedure and reuses it for later executions.
Parameter sniffing is simply this:
Typical symptoms:
The goal is not to “turn off parameter sniffing” globally, but to control when and how it happens.
Let’s build a quick example you can run on a test database.
-- Demo table with skewed data
CREATE TABLE dbo.Sales
(
SalesId INT IDENTITY(1,1) PRIMARY KEY,
CustomerId INT,
Region VARCHAR(20),
Amount DECIMAL(18,2),
SalesDate DATE
);
-- Skew: 90% of data in 'EU', 10% in 'US'
INSERT INTO dbo.Sales (CustomerId, Region, Amount, SalesDate)
SELECT TOP (90000)
ABS(CHECKSUM(NEWID())) % 10000,
'EU',
RAND(CHECKSUM(NEWID())) * 1000,
DATEADD(DAY, -ABS(CHECKSUM(NEWID())) % 365, CAST(GETDATE() AS DATE))
FROM sys.all_objects a CROSS JOIN sys.all_objects b;
INSERT INTO dbo.Sales (CustomerId, Region, Amount, SalesDate)
SELECT TOP (10000)
ABS(CHECKSUM(NEWID())) % 10000,
'US',
RAND(CHECKSUM(NEWID())) * 1000,
DATEADD(DAY, -ABS(CHECKSUM(NEWID())) % 365, CAST(GETDATE() AS DATE))
FROM sys.all_objects a CROSS JOIN sys.all_objects b;
CREATE INDEX IX_Sales_Region_SalesDate ON dbo.Sales (Region, SalesDate);
GO
CREATE OR ALTER PROC dbo.GetSalesByRegion
@Region VARCHAR(20)
AS
BEGIN
SET NOCOUNT ON;
SELECT Region, SUM(Amount) AS TotalAmount
FROM dbo.Sales
WHERE Region = @Region
GROUP BY Region;
END;
GO
``
Now run it twice:
```sql
EXEC dbo.GetSalesByRegion @Region = 'EU'; -- likely first sniff
EXEC dbo.GetSalesByRegion @Region = 'US'; -- reuses plan
Depending on which region runs first, you may see:
Either way, one of them will be suboptimal.
Older advice usually rotates around:
OPTION (RECOMPILE)WITH RECOMPILE on the procedureOPTIMIZE FOR UNKNOWNThese still work, but they have trade-offs:
RECOMPILE everywhere
OPTIMIZE FOR UNKNOWN
Local variable trick (copy parameter into a local variable)
UNKNOWN, plus harder to maintain.Modern SQL Server (2016+ with Query Store, 2017+ with automatic tuning) gives you more precise tools:
OPTIMIZE FOR hints to lock in good behavior per query.First, enable Query Store if it isn’t already.
ALTER DATABASE YourDbName SET QUERY_STORE = ON;
GO
ALTER DATABASE YourDbName SET QUERY_STORE
(
OPERATION_MODE = READ_WRITE,
CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 30),
DATA_FLUSH_INTERVAL_SECONDS = 900,
INTERVAL_LENGTH_MINUTES = 60,
MAX_STORAGE_SIZE_MB = 512,
QUERY_CAPTURE_MODE = AUTO
);
GO
Now run your procedure with different parameters multiple times.
EXEC dbo.GetSalesByRegion @Region = 'EU';
EXEC dbo.GetSalesByRegion @Region = 'US';
EXEC dbo.GetSalesByRegion @Region = 'US';
EXEC dbo.GetSalesByRegion @Region = 'EU';
Inspect Query Store for multiple plans and variable performance:
SELECT
qsq.query_id,
qsq.object_id,
qsp.plan_id,
qsp.last_execution_time,
qsp.avg_duration,
qsp.avg_cpu_time,
qsp.avg_logical_io_reads
FROM sys.query_store_query qsq
JOIN sys.query_store_plan qsp
ON qsq.query_id = qsp.query_id
WHERE qsq.object_id = OBJECT_ID('dbo.GetSalesByRegion')
ORDER BY qsp.avg_duration DESC;
What you’re looking for:
This is your confirmation that parameter sniffing is affecting real workloads.
Once you identify which plan is stable and performant for your typical workload, you can force it.
From the previous query, pick a plan_id that:
EXEC sys.sp_query_store_force_plan
@query_id = 123, -- replace with your query_id
@plan_id = 456; -- replace with your chosen plan_id
Now SQL Server will always use this plan unless you unforce it or it becomes invalid (e.g., schema change).
Pros:
Cons:
This is often the lowest-friction fix for production issues when you need stability fast.
OPTIMIZE FOR Hints in CodeFor queries you control, OPTIMIZE FOR lets you shape the plan based on realistic scenarios.
OPTIMIZE FOR UNKNOWNApplied at statement level:
CREATE OR ALTER PROC dbo.GetSalesByRegion_Unknown
@Region VARCHAR(20)
AS
BEGIN
SET NOCOUNT ON;
SELECT Region, SUM(Amount) AS TotalAmount
FROM dbo.Sales
WHERE Region = @Region
GROUP BY Region
OPTION (OPTIMIZE FOR (@Region UNKNOWN));
END;
This tells the optimizer: “don’t sniff the actual parameter value; use general stats instead.”
Use when:
OPTIMIZE FOR specific valuesIf your workload is dominated by one or two parameter values, you can optimize for those explicitly.
CREATE OR ALTER PROC dbo.GetSalesByRegion_Optimized
@Region VARCHAR(20)
AS
BEGIN
SET NOCOUNT ON;
SELECT Region, SUM(Amount) AS TotalAmount
FROM dbo.Sales
WHERE Region = @Region
GROUP BY Region
OPTION (OPTIMIZE FOR (@Region = 'EU'));
END;
This locks the plan to the cardinality estimates for @Region = 'EU'.
Use when:
EU) accounts for most traffic.RECOMPILE selectivelyFor some edge cases, you might mix strategies:
SELECT Region, SUM(Amount) AS TotalAmount
FROM dbo.Sales
WHERE Region = @Region
GROUP BY Region
OPTION (OPTIMIZE FOR (@Region UNKNOWN), RECOMPILE);
This is more expensive but can be valid for:
On newer SQL Server versions, Query Store supports query store hints: you can apply hints like OPTIMIZE FOR UNKNOWN to a query without editing the actual code.
Note: Exact syntax and availability depend on your SQL Server version; check your documentation and test in a non-production environment first.
Typical workflow:
OPTIMIZE FOR UNKNOWN).Conceptually, it looks like:
EXEC sys.sp_query_store_set_hints
@query_id = 123,
@value = N'OPTION (OPTIMIZE FOR (@Region UNKNOWN))';
Benefits:
Again, test thoroughly: hints can fix one issue and introduce another if used blindly.
A quick decision guide:
Production incident, no time, no code change allowed
You own the code, skewed parameters, predictable workload
OPTIMIZE FOR specific value if one parameter dominates.You own the code, unpredictable parameters
OPTIMIZE FOR UNKNOWN and validate with Query Store metrics.Vendor app or legacy stack, but you have Query Store
High-frequency OLTP vs. low-frequency reporting
RECOMPILE.RECOMPILE or aggressive hints.Don’t wait for the next production fire. Build a small, repeatable playbook:
OPTIMIZE FOR or Query Store hints (longer-term).If you turn this into a habit, parameter sniffing becomes a controlled behavior rather than a random performance surprise.
MS-SQL
New
Next Batches Now Live
Power BI
SQL
Power Apps
Power Automate
Microsoft Fabrics
Azure Data Engineering