Articles

What Is a Stored Procedure? SQL Logic in the Database

A stored procedure is precompiled SQL saved inside the database and invoked by name, cutting network round trips and centralizing business logic.

The Lycoris Team The Lycoris Team · · 5 min read
Abstract illustration of database tables and connections

A stored procedure is a named, precompiled block of SQL — often with parameters, conditionals, and loops — that lives inside the database itself and runs there when called, instead of being sent over the wire statement by statement from an application. Think of it as a function, but one the database engine owns and executes close to the data.

What a stored procedure actually is

Most SQL you write in an application ends up as a query string: your app builds it, sends it over the network, the database parses and plans it, then returns rows. A stored procedure flips part of that flow. You define the logic once, inside the database, with CREATE PROCEDURE (or CREATE FUNCTION in some systems), and from then on the application calls it by name with arguments — CALL update_inventory(@product_id, @qty) — rather than shipping the full SQL text every time.

The procedure body can do more than a single query. It can branch, loop, handle errors, and run several statements as one atomic unit, all without leaving the database server. That’s the core difference from an ordinary parameterized query: a stored procedure is a program, not just a statement.

Why call into the database instead of the app

The traditional argument for stored procedures is round trips. If a business operation needs five queries — check stock, decrement it, insert an order row, update a customer total, log the change — running that logic in the application means five separate network hops between app and database, each with its own latency. Wrap the same steps in a stored procedure and the application makes one call; everything after that happens inside the database process.

That matters more as the query count per operation grows, and it compounds under load: shaving four round trips off a hot path multiplies across every request. It also means the logic runs with transactional guarantees enforced natively — see what ACID transactions guarantee for why atomicity across multiple statements is worth having close to the data rather than coordinated from the app tier.

Centralization is the other classic argument. If five different services all need to apply the same discount rule or validation, putting that rule in a stored procedure means every caller — regardless of language or framework — gets identical behavior. There’s no risk of one team’s ORM logic drifting from another’s hand-written SQL.

Stored procedures vs application-layer logic

Stored proceduresApplication-layer logic
Where it runsInside the database engineIn app servers, outside the DB
Round tripsOne call for multi-step logicOne round trip per query, typically
Version controlOften lives in DB migration scriptsLives with the rest of the codebase
TestingRequires a real (or test) database instanceEasier to unit test in isolation
PortabilityTied to one database’s dialectPortable across database vendors
DebuggingVendor-specific tools, harder to step throughStandard debuggers, breakpoints, stack traces
Scaling logicScales with the database serverScales independently, horizontally

Neither column is strictly better — they trade round-trip cost and centralization against portability and developer ergonomics.

The tradeoffs vendors don’t put on the box

Stored procedures pull business logic out of your application’s version control and into the database. That’s the whole appeal, and it’s also the catch: procedural SQL — PL/pgSQL, T-SQL, PL/SQL — is a different language from the one your team writes application code in, with its own debugging story and much weaker tooling for tracing, testing, and refactoring than a modern application stack.

They’re also vendor-specific. A procedure written for PostgreSQL’s PL/pgSQL doesn’t run unmodified on SQL Server or MySQL, which matters if you ever plan to migrate — see Postgres vs MySQL for how much the surrounding ecosystems already diverge. And because the logic lives inside the database, it scales with the database server rather than with your application tier — you can’t just add more app containers to handle more procedure calls the way you can with stateless application code.

Deployment gets trickier too: procedures need their own migration and rollback story, layered on top of your schema migrations, and there’s no compiler catching a typo in a rarely-hit branch until that branch actually runs in production.

The security angle

Stored procedures are also a defense against SQL injection. When application code calls a procedure with typed parameters, the input is bound as data, never concatenated into a query string, so there’s no injection surface to exploit at that call site. This is the same protection parameterized queries offer, just enforced at the database boundary instead of the app layer — and it plays well with an ORM that wraps procedure calls behind a typed interface, giving you compile-time safety on the app side and injection safety on the database side simultaneously.

Some teams go further and restrict application database users to only calling specific procedures, with no direct table access at all. That turns the procedure layer into the entire API surface of the database — nothing gets read or written except through a reviewed, auditable entry point.

When they still make sense

Stored procedures aren’t the default they were twenty years ago — most teams now keep business logic in application code and use SQL joins or a query builder for anything that needs custom CTEs or complex joins. But they haven’t disappeared. They’re still the right tool when a multi-step operation needs strict transactional atomicity that’s awkward to coordinate from outside the database, when a hot path is genuinely round-trip-bound, or when a rule must be enforced identically no matter which of a dozen internal services touches the data.

The takeaway

A stored procedure trades portability and tooling for fewer round trips and a single, database-enforced source of truth for a piece of logic. That’s a good trade when several services must apply the same rule identically or when a multi-step operation can’t afford several network hops — and a bad one when you’d rather keep logic in your application’s language, version control, and test suite. Most modern systems lean toward the latter by default and reach for procedures only where the round-trip or consistency argument is concrete, not hypothetical.

The Lycoris Team The Lycoris Team · · 5 min read

What Is a Database Trigger?

A database trigger is a procedure that runs automatically on an insert, update, or delete — enforcing rules the application layer can't guarantee.

#Databases #SQL #Backend
The Lycoris Team The Lycoris Team · · 4 min read

Primary Key vs Foreign Key vs Unique Constraint

Primary keys identify a row, foreign keys link one table to another, and unique constraints just prevent duplicates. How the three differ in SQL.

#Databases #SQL #Backend
The Lycoris Team The Lycoris Team · · 4 min read

Database Deadlocks Explained: Causes and Prevention

A database deadlock happens when two transactions each wait on a lock the other holds. Why deadlocks occur, how databases detect them, and how to avoid them.

#Databases #SQL #Backend