SQL Stored Procedures: Parameter Sniffing - 10s to 4 Min
Parameter sniffing: a 500,000-order test account cached a table-scan plan, causing 4-minute waits for normal users.
- A stored procedure is a named, precompiled SQL block stored in the database — called by name, not by sending SQL text each time
- Benefits: precompiled execution plan, reduced network round-trips, centralised data logic, database-level security control
- Parameter sniffing: the plan cached on first call may be suboptimal for different parameter values on later calls
- Dynamic SQL inside procedures re-introduces injection risk — always use sp_executesql with parameters, never string concatenation
- SQL Server: avoid the sp_ prefix — it forces a master database lookup before checking the current database
- Use procedures for complex multi-step logic; use ORMs for straightforward CRUD — do not over-engineer simple operations
A stored procedure is like a saved recipe in the restaurant's official cookbook. Instead of every waiter describing the dish to the chef from scratch each time ('take two eggs, add flour, heat at 180...'), they just say 'make the carbonara'. The chef knows the recipe, has the tools ready, and executes it faster because the prep work was done in advance. Stored procedures work the same way — the database has the query precompiled and ready, so calling it is faster than sending raw SQL every time.
Every production database eventually hits the same wall: raw SQL queries scattered across application code become a maintenance nightmare and a security liability. Stored procedures solve this by separating the definition of data logic from its invocation. By encapsulating logic inside the database, you get precompiled execution plans, centralised maintenance, and database-level permission control.
However, stored procedures are not a universal solution. They couple business logic to the database layer, complicate version control and deployment pipelines, and introduce parameter sniffing problems that can make performance unpredictable. Understanding when to use them — and when to let your ORM handle the job — is a skill that separates pragmatic engineers from those who over-engineer or under-engineer their data layer.
Anatomy of a Production-Ready Stored Procedure
A robust stored procedure handles more than just running a query. It validates inputs, wraps operations in a transaction, handles errors explicitly, and returns meaningful result codes to the caller. The pattern below is a standard template for secure, transactional procedures.
SET NOCOUNT ON at the top suppresses the 'N rows affected' messages that can interfere with application code that checks rowcounts. BEGIN TRY / BEGIN CATCH provides structured error handling — without it, a runtime error leaves the transaction open and the caller gets an obscure error code. XACT_STATE() inside the catch checks whether the transaction is still committable before attempting a rollback.
XACT_STATE() in the CATCH block is critical — if the transaction is in an uncommittable state (-1), you cannot commit, you can only roll back. Missing this check causes silent data integrity failures.XACT_STATE() are the three non-negotiable elements of a production-ready stored procedure.Parameter Sniffing — The Silent Performance Trap
When SQL Server first executes a stored procedure, it compiles an execution plan optimised for the specific parameter values provided on that call. This plan is then cached and reused for all subsequent calls, regardless of whether the new parameters would benefit from a different plan. This is parameter sniffing.
For most procedures, sniffing is beneficial — the compiled plan is reused without the overhead of recompilation. The problem occurs when the data distribution is highly variable. A parameter value that returns 5 rows might benefit from an index seek. A parameter value that returns 5 million rows might benefit from a table scan. When the cached plan is optimised for one extreme, it performs terribly for the other.
The three main fixes: WITH RECOMPILE forces a fresh plan on every call (eliminates the benefit of caching but prevents stale plans). OPTION(OPTIMIZE FOR UNKNOWN) generates a plan based on column statistics rather than the sniffed parameter value — a compromise. Local variables instead of direct parameters prevent sniffing entirely by breaking the parameter-value link.
When to Use Stored Procedures vs ORMs vs Inline SQL
Stored procedures are not always the right tool. The choice depends on the complexity of the logic, your team's SQL proficiency, deployment pipeline maturity, and whether you need database-level security.
Stored procedures are the right choice when: you need to grant users or services access to specific operations without exposing the underlying tables (database-level security); the logic involves multiple steps that must be atomic and the business rules are unlikely to change frequently; performance is critical and you want precompiled plans with minimal network overhead; or you are working in a corporate environment where database changes go through a DBA-controlled deployment process.
ORMs are the right choice for: straightforward CRUD operations where the ORM generates correct, parameterised SQL; when rapid iteration is needed and SQL refactoring via migrations is part of your workflow; teams where SQL expertise is limited and the ORM abstraction reduces errors.
Inline parameterised SQL is appropriate for: complex queries that are unique to one endpoint and do not warrant a named procedure; when the query logic needs to live with the application code for maintainability.
Parameter Sniffing Caused a 10-Second Stored Procedure to Randomly Take 4 Minutes
- Parameter sniffing is silent — the procedure appears to work correctly until an atypical parameter value poisons the cached plan
- Use WITH RECOMPILE for procedures with highly variable parameter distributions (e.g., customer IDs spanning 5 to 500,000 rows)
- Monitor procedure execution statistics with sys.dm_exec_procedure_stats to detect plan cache issues proactively
Key takeaways
XACT_STATE() are non-negotiable in every production stored procedure.Common mistakes to avoid
3 patternsUsing the sp_ prefix for custom stored procedures in SQL Server
Ignoring parameter sniffing for procedures with variable data distributions
Using dynamic SQL with string concatenation inside a stored procedure
Interview Questions on This Topic
What is parameter sniffing in SQL Server and how do you fix it?
Frequently Asked Questions
That's SQL Advanced. Mark it forged?
3 min read · try the examples if you haven't