Stored procedures are named blocks of logic saved inside the database itself, callable by name instead of by sending the full SQL text over the network every time. Instead of an application building and sending a multi-statement script on every request, it sends one short call — CALL apply_monthly_charges() — and the database runs the logic that already lives there. The idea predates most of today's application frameworks, fell out of fashion as "logic belongs in the app, not the database" became conventional wisdom, and is still genuinely useful for a narrower set of cases than it once was used for.
What changed in 2026
- Postgres and MySQL both continued closing the procedural-language gap. PL/pgSQL and MySQL's stored routine syntax remain the two dominant dialects, and neither has become portable — moving procedures between engines is still a rewrite, not a copy-paste.
- AI coding assistants made writing procedural SQL far less painful, lowering the barrier that used to push teams toward "just do it in the app" by default.
- More teams version-control procedure definitions as migrations, treating a
CREATE OR REPLACE FUNCTION the same as any schema change, which closes much of the old "invisible logic" complaint.
What a stored procedure actually is
A minimal Postgres example:
CREATE OR REPLACE PROCEDURE apply_monthly_charges()
LANGUAGE plpgsql
AS $
BEGIN
UPDATE subscriptions
SET balance_cents = balance_cents + plan_price_cents
WHERE status = 'active';
INSERT INTO billing_events (subscription_id, amount_cents, created_at)
SELECT id, plan_price_cents, now()
FROM subscriptions
WHERE status = 'active';
END;
$;
CALL apply_monthly_charges();
Everything inside runs as one unit on the database server, close to the data, without shipping intermediate results back and forth to the application.
Stored procedure vs function vs plain query
|
Stored procedure |
Function |
Plain query from the app |
| Called with |
CALL |
SELECT or inline in a query |
Sent directly from app code |
| Can have side effects (INSERT/UPDATE) |
Yes |
Engine-dependent, often restricted |
Yes |
| Returns a value directly |
Not always |
Yes, by definition |
Yes |
| Can manage its own transaction |
Yes, in most engines |
No, runs inside caller's transaction |
Managed by the app |
| Lives in version control |
Only if you deliberately migrate it |
Same |
Naturally, as app code |
When stored procedures earn their keep
The clearest case is a multi-step operation that must run as close to the data as possible to avoid shipping large intermediate results across the network — bulk recalculations, end-of-period batch jobs, or logic that several different applications and scripts all need to invoke identically. Centralizing it as a procedure means every caller gets the same behavior without re-implementing it, and permissions can be granted on the procedure itself rather than on the underlying tables, which is a genuine security win: an application can be allowed to CALL apply_monthly_charges() without ever having direct UPDATE rights on the subscriptions table. This pairs naturally with a database transaction, since a procedure's internal steps typically need to succeed or fail together.
The case against them
The honest downside is tooling and visibility. Application code lives in your repository, goes through the same review and CI as everything else, and shows up in git blame. Procedure logic, unless a team is deliberate about migrating it the same way, tends to drift — someone edits it directly in a production console during an incident, and now the deployed logic no longer matches anything in source control. It also locks business logic to a specific database engine's procedural dialect, which is a real cost if a migration to a different engine is ever on the table. Many of the same objections that apply to a database trigger — hidden logic, harder debugging — apply here too, just without the "runs automatically" part that makes triggers uniquely risky.
FAQ
Are stored procedures faster than doing the same work in application code?
Often yes for logic that would otherwise require several network round trips, since the procedure runs entirely on the server. For a single simple query, the difference is negligible.
Can a stored procedure call another stored procedure?
Yes, in every mainstream engine, and this is how teams build up reusable, composable server-side logic — with the same "hidden call graph" caution that applies to nested triggers.
Is a stored procedure the same thing across Postgres, MySQL, and SQL Server?
No. The concept is the same, but the procedural language differs — PL/pgSQL, MySQL's stored routine syntax, and T-SQL are not interchangeable, and moving a procedure between engines means rewriting it.
Do ORMs support calling stored procedures?
Most do, though usually as an escape hatch (raw SQL execution) rather than a first-class citizen, because ORMs are generally built around generating queries rather than calling pre-written ones.
Where to go next