Cardinality Estimation and Parameter Sniffing in SQL Server Explained

Cardinality Estimation and Parameter Sniffing in SQL Server Explained

Every plan the optimizer builds rests on a guess: how many rows will this operator produce? When that guess is wrong, the whole plan can be wrong — and parameter sniffing is the single most common way it happens.

Parameter Sniffing, sketched out(same cached plan, very different data)ShippedDeliveredCancelledReturnedPending① compiled: 50 rows③ reused: 3,000,000 rows① First call: ‘cancelled’50 rows estimated — Seek plancompiled AND cachedplan cache② Second call: ‘pending’3,000,000 rows — SAME seek planreused — catastrophically slowDiagnose with WITH RECOMPILE.Fix via OPTION(RECOMPILE) or splitprocs — trades CPU vs stability. 📌

Cardinality Estimation, in One Sentence

The Cardinality Estimator uses statistics (histograms on indexed/queried columns) to predict row counts at each step of a plan, and picks join algorithms, index usage, and memory grants based on those predictions — not the real data, which it hasn’t seen yet.

Parameter Sniffing: A Concrete Example

This is the exact mechanism previewed back in the Developers & DBAs course’s stored-procedure lesson — the precompilation that makes procedures fast is the same precompilation that causes this.

CREATE PROCEDURE dbo.usp_GetOrdersByStatus @status NVARCHAR(20)
AS
SELECT * FROM dbo.OrderLog WHERE order_status = @status;

-- First call: 'cancelled' matches 50 rows out of 5 million — compiles a Seek-based plan
EXEC dbo.usp_GetOrdersByStatus @status = 'cancelled';

-- Second call: 'pending' matches 3 million rows — REUSES the seek-based plan, now terrible
EXEC dbo.usp_GetOrdersByStatus @status = 'pending';

What’s Actually Happening

First compile: ‘cancelled’ Estimated: 50 rows Plan: Index Seek (correct fit) Plan cached for this proc Reused for: ‘pending’ Actual: 3,000,000 rows Still uses the seek plan — catastrophically slow

Diagnosing It

-- Compare estimated vs actual rows in the plan (Ctrl+M in SSMS)
-- A huge gap on a parameterized proc call is the signature of parameter sniffing

-- Force a fresh compile per call to test the theory (not a permanent fix, a diagnostic)
EXEC dbo.usp_GetOrdersByStatus @status = 'pending' WITH RECOMPILE;

If the RECOMPILE version is fast, you’ve confirmed parameter sniffing. Real fixes include OPTION (RECOMPILE) on the specific statement, query hints, or splitting into separate procedures for genuinely different data distributions — each with real trade-offs in CPU cost vs plan stability.


Enjoyed this?

Subscribe to get every new lesson as soon as it’s published, and share it with a developer who’d find it useful.

📡 Subscribe via RSS  | 
Share on X  | 
Share on LinkedIn  | 
Share on Facebook

Want the full structured course with quizzes and hands-on labs? Check out SQL Server Performance Tuning, coming soon on this site.