Tag: Parameter Sniffing

  • 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.