Incident brief
ROLLBACK TO an undeclared savepoint
ROLLBACK TO SAVEPOINT (or RELEASE SAVEPOINT) fails if the named savepoint was never established in the current transaction, raising this savepoint exception.
In 10 seconds
- What
- ROLLBACK TO an undeclared savepoint
- What triggers it
- Open a transaction and issue ROLLBACK TO SAVEPOINT <name> without having declared that savepoint.
- The fix
- Declare the savepoint (SAVEPOINT <name>) before rolling back to it.
- Proof
- Reproduced on PostgreSQL 18.4 → Inside a transaction the reproduction rolls back to a savepoint that was never declared, raising SQLSTATE 3B001.
The fix
What to do right now
The immediate, application-level response to this error.
- Declare the savepoint (SAVEPOINT <name>) before rolling back to it.
- Match every ROLLBACK TO or RELEASE to a savepoint actually established earlier in the transaction.
-- Establish the savepoint before rolling back to it.
BEGIN;
SAVEPOINT before_charge;
ROLLBACK TO SAVEPOINT before_charge;
COMMIT;Diagnose
See it live on the server
Run these against the affected instance to confirm the diagnosis before you act.
Standard triage — not specific to this error
These are canonical PostgreSQL system-catalog queries, shown as SQL to run. No sample output is attached because this is general triage, not a captured lab transcript.This SQLSTATE does not have an error-specific live snapshot yet. These are the canonical system-catalog queries you run against the affected server to see the problem in real time — standard triage, not a reproduced transcript.
What is running right now
Active backends, how long each has been running, and what it is waiting on.
SELECT pid,
state,
wait_event_type,
wait_event,
now() - query_start AS running_for,
left(query, 80) AS query
FROM pg_stat_activity
WHERE state <> 'idle'
AND pid <> pg_backend_pid()
ORDER BY running_for DESC NULLS LAST;Who is blocking whom
Turn raw blocking PIDs into the actual queries on both sides of the wait.
SELECT blocked.pid AS blocked_pid,
blocked.query AS blocked_query,
blocking.pid AS blocking_pid,
blocking.query AS blocking_query
FROM pg_stat_activity AS blocked
JOIN LATERAL unnest(pg_blocking_pids(blocked.pid)) AS b(pid) ON true
JOIN pg_stat_activity AS blocking ON blocking.pid = b.pid
WHERE cardinality(pg_blocking_pids(blocked.pid)) > 0;Locks that are still waiting
Every lock a backend has requested but not yet been granted.
SELECT l.pid,
l.locktype,
l.mode,
l.granted,
COALESCE(c.relname, l.transactionid::text) AS object
FROM pg_locks l
LEFT JOIN pg_class c ON c.oid = l.relation
WHERE NOT l.granted
ORDER BY l.pid;Why it happens
What PostgreSQL is telling you
The mechanism behind the error, grounded in the official manual — not paraphrased.
PostgreSQL 18 Documentation — Appendix A. PostgreSQL Error Codes (Table A.1, Class 3B — Savepoint Exception)
3B001 → invalid_savepoint_specificationRead the full section on postgresql.org →
Rolling back to a missing savepoint
No SAVEPOINT before_charge was established in this transaction, so ROLLBACK TO raises 'savepoint before_charge does not exist'.Recovering the aborted transaction
The error aborted the open transaction, so the session must ROLLBACK before it accepts new work; the next statement then runs normally.Reproduce & verify
A real, single-session PostgreSQL reproduction
A literal transcript of SQL run against a live PostgreSQL instance in an isolated lab — the commands below are exactly what was executed.
- 1Open a transaction and issue ROLLBACK TO SAVEPOINT <name> without having declared that savepoint.
- 2PostgreSQL looks for the named savepoint in the current transaction and finds none.
- 3The statement aborts with SQLSTATE 3B001: 'savepoint does not exist'.
Error-handling code rolled back to a savepoint name that a skipped branch never actually created.
-- The error is self-contained in one statement; no schema is required.
SELECT 'no schema needed' AS setup_note;BEGIN;
ROLLBACK TO SAVEPOINT before_charge;ROLLBACK;
SELECT 'ok' AS session_after_rollback;What PostgreSQL actually returned
setup_note
------------------
no schema needed
(1 row)BEGIN
ERROR: savepoint "before_charge" does not existROLLBACK
session_after_rollback
------------------------
ok
(1 row)The rollback-to-savepoint works once the savepoint is declared first.
We declare the savepoint, then roll back to it within the same transaction.
Without this
Before: rolling back to a missing savepoint aborts
With this, tested
After: declare then roll back
What Pro unlocks here
- The exact prevention SQL — copy-paste ready
- Raw psql output captured from the Docker lab
- A senior-DBA action list to take it further
- Live monitoring queries to catch it in production
- The deeper audit: fix-that-fails counterexample, GUC before/after, server-log evidence
Related & next steps
Follow the thread
Everything this error touches — jump straight to the sibling error, term, runbook, or parameter.
Verification
- Last verified
- 2026-07-24 (isolated lab, PostgreSQL 18.4)
- Reviewed by
- Verified against PostgreSQL 18.4 in an isolated lab environment
- Audit status
- reviewed