PostgreSQL raises SQLSTATE 0B000 (invalid_transaction_initiation) when a BEGIN or START TRANSACTION command is issued in a context where starting a new transaction is not permitted. The server returns an error such as:
WARNING: there is already a transaction in progress
or, in certain procedural contexts:
ERROR: invalid transaction initiation
The condition name is invalid_transaction_initiation and it belongs to SQLSTATE class 0B — a standalone class defined in the SQL standard for transaction initiation errors.
What This Error Means
In PostgreSQL, every statement outside of an explicit transaction block runs in its own implicit single-statement transaction. When a client explicitly issues BEGIN or START TRANSACTION, PostgreSQL enters a multi-statement transaction block. Issuing another BEGIN while already inside a transaction block is not allowed by the SQL standard, and PostgreSQL enforces this.
In interactive psql sessions, PostgreSQL typically downgrades this to a WARNING ("there is already a transaction in progress") and continues rather than aborting. However, in procedural code — PL/pgSQL functions, procedures, or trigger functions — attempting to start a transaction in a context that does not support it raises a hard ERROR with SQLSTATE 0B000.
The most common procedural scenario is calling BEGIN inside a PL/pgSQL function body. Ordinary PL/pgSQL functions always execute within the calling transaction; they cannot start or commit their own transactions. Only procedures invoked with CALL (introduced in PostgreSQL 11) and DO blocks can manage their own transactions using COMMIT and ROLLBACK — and even then, only when called from a top-level context, not from within another transaction block.
Common Causes
Calling
BEGINinside a PL/pgSQL function. Functions run as part of their caller's transaction and do not have the authority to start a new one. AnyBEGINstatement in function body code triggers this error.Using
BEGINinside a procedure called from within a transaction block. Even though procedures can manage transactions, they cannot do so if theCALLstatement itself was made inside an open transaction. The caller's transaction takes precedence.Application-level double
BEGIN. A connection pooler or ORM issuesBEGINbefore calling a stored procedure or function that also issuesBEGINinternally. This commonly surfaces in frameworks that automatically wrap calls in transactions.Trigger functions attempting to control transactions. Trigger functions execute inside the transaction that fired the trigger and cannot initiate new transactions.
DOblock issued inside an existing transaction. ADOblock that callsCOMMITor uses transaction control will fail if it was started inside an open transaction.
How to Fix invalid_transaction_initiation
Remove
BEGINfrom PL/pgSQL function bodies. Functions cannot control transactions — remove anyBEGIN/COMMIT/ROLLBACKstatements and let the caller manage the transaction:-- Wrong: BEGIN inside a function CREATE OR REPLACE FUNCTION do_work() RETURNS void AS $$ BEGIN BEGIN; -- ERROR: invalid transaction initiation INSERT INTO logs(msg) VALUES ('started'); COMMIT; END; $$ LANGUAGE plpgsql; -- Correct: remove transaction control from the function CREATE OR REPLACE FUNCTION do_work() RETURNS void AS $$ BEGIN INSERT INTO logs(msg) VALUES ('started'); END; $$ LANGUAGE plpgsql;Convert the function to a procedure if transaction control is needed. Procedures (PostgreSQL 11+) called with
CALLfrom a top-level context can useCOMMITandROLLBACK:CREATE OR REPLACE PROCEDURE do_work_with_txn() LANGUAGE plpgsql AS $$ BEGIN INSERT INTO logs(msg) VALUES ('step 1'); COMMIT; INSERT INTO logs(msg) VALUES ('step 2'); COMMIT; END; $$; -- Call from a top-level context (not inside BEGIN...COMMIT) CALL do_work_with_txn();Ensure the procedure is called outside any transaction block. If an application framework wraps every call in
BEGIN/COMMIT, disable that behavior for the specificCALLstatement, or refactor the logic so that transaction management happens at only one layer.Check ORM and connection pooler settings. Frameworks like SQLAlchemy, Django ORM, ActiveRecord, and connection poolers like PgBouncer or pgpool-II may issue automatic
BEGINstatements. Inspect the actual SQL being sent (enablelog_statements = 'all'temporarily) to confirm whether aBEGINis already in flight before your code issues another one.
Additional Information
- SQLSTATE class
0Bcontains only the single conditioninvalid_transaction_initiation— there are no sibling codes within this class. - Transaction control in procedures (
COMMIT/ROLLBACKwithinCALL) was introduced in PostgreSQL 11. On earlier versions, no server-side code could manage transactions at all. - Related transaction-state errors include
25001(active_sql_transaction) — raised when a command requires no active transaction but one exists — and25P02(in_failed_sql_transaction) — raised when a command is issued after a transaction has already entered an error state. - Most PostgreSQL client drivers (libpq, psycopg2, asyncpg, node-postgres) surface this as a database exception containing the SQLSTATE
0B000. The exact Python exception class ispsycopg2.errors.InvalidTransactionInitiation. - In
psql, the interactive client, issuingBEGINtwice results in aWARNINGrather than an error, which can mask the problem during manual testing but still cause failures in application code.
Frequently Asked Questions
Why does BEGIN work fine in psql but fail in my application code?
In psql, a redundant BEGIN is downgraded to a warning and execution continues. Inside PL/pgSQL functions or procedures called within a transaction, PostgreSQL enforces the restriction as a hard error. The difference is context: psql is a client issuing top-level commands; a function body runs server-side within a transaction that the function does not own.
Can I use SAVEPOINT instead of nested BEGIN?
Yes. PostgreSQL does not support true nested transactions, but it does support savepoints within a transaction block. Use SAVEPOINT name, RELEASE SAVEPOINT name, and ROLLBACK TO SAVEPOINT name to create rollback points inside a transaction without starting a new one.
BEGIN;
INSERT INTO orders(item) VALUES ('widget');
SAVEPOINT before_payment;
INSERT INTO payments(amount) VALUES (9.99);
-- If payment fails:
ROLLBACK TO SAVEPOINT before_payment;
COMMIT;
My procedure needs to commit partway through. Is that supported?
Yes, but only when the procedure is called from outside any transaction block. Call CALL my_procedure() directly from your application without wrapping it in BEGIN/COMMIT. Inside the procedure, use COMMIT and ROLLBACK freely. If the CALL is inside an open transaction, PostgreSQL will raise an error when the procedure tries to commit.
How do I find out which layer is issuing the extra BEGIN?
Enable verbose query logging temporarily:
ALTER SYSTEM SET log_min_messages = 'debug1';
ALTER SYSTEM SET log_statements = 'all';
SELECT pg_reload_conf();
Then check postgresql.log. You will see every statement the server receives, making it straightforward to identify which client or framework is issuing BEGIN before your code does. Remember to reset these settings afterward, as they generate significant log volume.