2026년 08월 05일 | DBMS Error 가이드
이 글에서 다루는 내용
0B000 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
0B000 invalid transaction initiation 는?
PostgreSQL 에러 코드 0B000 invalid transaction initiation은 트랜잭션을 시작하는 방식이 현재 컨텍스트에서 유효하지 않을 때 발생하는 에러입니다. 주로 이미 트랜잭션 블록 내부에 있는 상태에서 중첩된 트랜잭션을 명시적으로 시작하려 하거나, 트랜잭션을 허용하지 않는 특수한 컨텍스트(예: 특정 PL/pgSQL 함수 내부, 트리거 내부)에서 BEGIN 명령을 실행할 때 나타납니다. 이 에러는 PostgreSQL의 트랜잭션 모델과 실행 컨텍스트에 대한 이해 부족에서 비롯되는 경우가 많으며, 운영 환경에서 갑작스러운 서비스 중단을 유발할 수 있어 빠른 원인 파악과 해결이 필요합니다.
주요 발생 원인
1. 트랜잭션 블록 내부에서 BEGIN 중첩 실행
가장 흔한 원인으로, 이미 BEGIN 또는 START TRANSACTION으로 트랜잭션이 시작된 상태에서 다시 BEGIN을 호출하는 경우입니다. PostgreSQL은 기본적으로 중첩 트랜잭션(nested transaction)을 직접 지원하지 않으며, 중첩된 BEGIN은 경고 메시지 또는 에러를 발생시킵니다. 애플리케이션 코드나 ORM 라이브러리에서 트랜잭션 상태를 추적하지 않고 무조건 BEGIN을 호출하면 이 문제가 발생합니다.
2. 트리거(Trigger) 또는 함수 내부에서 트랜잭션 제어 명령 실행
PL/pgSQL로 작성된 트리거 함수나 일반 함수 내부에서 BEGIN, COMMIT, ROLLBACK 같은 트랜잭션 제어 명령을 직접 사용하면 이 에러가 발생합니다. 트리거는 이미 기존 트랜잭션의 일부로 실행되기 때문에, 내부에서 새로운 트랜잭션을 시작하는 것은 PostgreSQL의 실행 모델과 충돌합니다. 특히 레거시 코드를 Oracle에서 PostgreSQL로 마이그레이션하는 과정에서 Oracle의 autonomous transaction 패턴을 그대로 옮기려다 이 에러를 마주치는 경우가 많습니다.
3. 자동 커밋(Autocommit) 모드 비활성화 상태의 라이브러리 동작과의 충돌
psycopg2, JDBC 등 데이터베이스 드라이버나 ORM(예: SQLAlchemy, Hibernate)은 기본적으로 자동 커밋을 비활성화하고 암묵적으로 트랜잭션을 시작합니다. 이 상태에서 애플리케이션 코드가 명시적으로 BEGIN을 추가로 호출하거나, 연결 풀링 환경에서 이전 연결의 트랜잭션이 정리되지 않은 채로 재사용되면 0B000 에러가 발생합니다. 연결 풀에서 반환된 커넥션이 이전 세션의 트랜잭션 상태를 그대로 가지고 있는 경우가 특히 위험합니다.
해결 방법
원인 1: 중첩 BEGIN 문제 해결
현재 트랜잭션 상태를 확인한 후 조건부로 트랜잭션을 시작하거나, SAVEPOINT를 활용하여 논리적 중첩 트랜잭션을 구현합니다.
-- 현재 트랜잭션 상태 확인
SELECT current_setting('transaction_isolation'), txid_current_if_assigned();
-- 잘못된 예시 (에러 발생 가능)
BEGIN;
INSERT INTO orders (customer_id, amount) VALUES (1, 100.00);
BEGIN; -- 에러! 이미 트랜잭션 내부에 있음
INSERT INTO order_items (order_id, product_id) VALUES (1, 5);
COMMIT;
-- 올바른 예시: SAVEPOINT로 중첩 트랜잭션 효과 구현
BEGIN;
INSERT INTO orders (customer_id, amount) VALUES (1, 100.00);
SAVEPOINT order_savepoint;
INSERT INTO order_items (order_id, product_id) VALUES (1, 5);
-- 문제가 생기면 savepoint로 롤백
-- ROLLBACK TO SAVEPOINT order_savepoint;
RELEASE SAVEPOINT order_savepoint;
COMMIT;
원인 2: 트리거 및 함수 내 트랜잭션 제어 제거
트리거 함수 내부에서 트랜잭션 제어 명령을 제거하고, 독립적인 트랜잭션이 필요한 경우 dblink 또는 PostgreSQL 11 이상에서 제공하는 프로시저(PROCEDURE)의 트랜잭션 제어 기능을 활용합니다.
-- 잘못된 트리거 함수 예시
CREATE OR REPLACE FUNCTION bad_trigger_func()
RETURNS TRIGGER AS $$
BEGIN
BEGIN; -- 에러! 트리거 내부에서 BEGIN 불가
INSERT INTO audit_log (table_name, action) VALUES (TG_TABLE_NAME, TG_OP);
COMMIT; -- 에러!
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 올바른 트리거 함수 예시
CREATE OR REPLACE FUNCTION correct_trigger_func()
RETURNS TRIGGER AS $$
BEGIN
-- 트랜잭션 제어 명령 없이 직접 작업 수행
INSERT INTO audit_log (table_name, action, changed_at)
VALUES (TG_TABLE_NAME, TG_OP, NOW());
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER audit_trigger
AFTER INSERT OR UPDATE OR DELETE ON orders
FOR EACH ROW EXECUTE FUNCTION correct_trigger_func();
-- PostgreSQL 11+에서 PROCEDURE를 통한 트랜잭션 제어 (자율 트랜잭션 대안)
CREATE OR REPLACE PROCEDURE log_with_transaction(
p_table_name TEXT,
p_action TEXT
)
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO audit_log (table_name, action, changed_at)
VALUES (p_table_name, p_action, NOW());
COMMIT; -- PROCEDURE 내부에서는 가능
END;
$$;
원인 3: 드라이버 및 연결 풀 트랜잭션 상태 정리
-- 현재 열려 있는 트랜잭션 세션 확인
SELECT pid, usename, application_name, state, query_start, state_change, query
FROM pg_stat_activity
WHERE state IN ('idle in transaction', 'idle in transaction (aborted)')
ORDER BY query_start;
-- 문제가 되는 세션 강제 종료 (운영 환경에서 주의하여 사용)
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
AND state_change < NOW() - INTERVAL '10 minutes';
-- 애플리케이션 코드에서 안전한 트랜잭션 패턴 (psycopg2 예시 SQL 관점)
-- 연결 반환 전 반드시 트랜잭션 정리
ROLLBACK; -- 불확실한 상태의 커넥션 초기화
-- 이후 정상적으로 새 트랜잭션 시작
BEGIN;
SELECT 1; -- 실제 업무 쿼리
COMMIT;
예방 방법
1. 트랜잭션 상태 추적 미들웨어 또는 래퍼 함수 구현
애플리케이션 레벨에서 트랜잭션 상태를 항상 추적하고, BEGIN을 호출하기 전에 현재 연결이 트랜잭션 내부에 있는지 확인하는 로직을 구현하세요. ORM을 사용하는 경우 ORM의 트랜잭션 관리 API만 사용하고, 원시 SQL로 BEGIN/COMMIT을 직접 호출하는 것을 피해야 합니다. 연결 풀을 사용하는 환경에서는 커넥션을 풀에 반환하기 전에 반드시 ROLLBACK 또는 COMMIT으로 트랜잭션을 마무리하는 것을 표준 절차로 정립하세요.
2. idle in transaction 세션 자동 타임아웃 설정
idle_in_transaction_session_timeout 파라미터를 설정하여 오래된 미완료 트랜잭션을 자동으로 종료시키는 정책을 적용하세요. 이 설정은 트랜잭션이 열린 채로 방치된 연결이 다음 요청에서 충돌을 일으키는 것을 방지합니다.
-- postgresql.conf 또는 세션 레벨 설정
-- 10분 이상 트랜잭션이 idle 상태면 자동 종료
SET idle_in_transaction_session_timeout = '10min';
-- 데이터베이스 레벨로 영구 적용
ALTER DATABASE myapp_db SET idle_in_transaction_session_timeout = '10min';
-- 현재 설정 확인
SHOW idle_in_transaction_session_timeout;
관련 에러
- 25000 (invalid_transaction_state): 현재 트랜잭션 상태에서 허용되지 않는 작업을 시도할 때 발생하며,
0B000과 함께 트랜잭션 컨텍스트 오류의 상위 범주에 속합니다. - 25001 (active_sql_transaction): 이미 활성화된 SQL 트랜잭션이 존재하는 상태에서 해당 컨텍스트에서 허용되지 않는 명령을 실행할 때 발생합니다.
- 25P02 (in_failed_sql_transaction): 이미 오류가 발생한 트랜잭션 블록 안에서 추가 명령을 실행하려 할 때 나타나는 에러로,
ROLLBACK없이 계속 명령을 실행하면 발생합니다. - 2D000 (invalid_transaction_termination): 트랜잭션 종료 방식이 잘못된 경우 발생하며, 프로시저나 함수 내에서 잘못된 COMMIT/ROLLBACK 패턴 사용 시 함께 마주칠 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.