2026년 08월 26일 | DBMS Error 가이드
이 글에서 다루는 내용
25000 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
25000 invalid transaction state 는?
PostgreSQL 에러 코드 25000 (invalid_transaction_state) 은 트랜잭션이 현재 상태에서 허용되지 않는 명령이나 작업을 수행하려 할 때 발생합니다. 쉽게 말해, 트랜잭션의 생명주기(lifecycle) 상 올바르지 않은 시점에 SQL 명령을 실행했을 때 PostgreSQL이 이를 감지하고 거부하는 에러입니다. 예를 들어, 이미 종료된 트랜잭션 내에서 추가 작업을 시도하거나, 트랜잭션 블록 밖에서 특정 트랜잭션 제어 명령을 잘못 사용하는 경우가 대표적입니다.
주요 발생 원인
1. 이미 오류가 발생한 트랜잭션 내에서 추가 쿼리 실행
PostgreSQL에서는 트랜잭션 블록(BEGIN ~ COMMIT) 내에서 한 번 에러가 발생하면, 해당 트랜잭션은 “aborted” 상태로 전환됩니다. 이 상태에서 ROLLBACK 없이 추가 쿼리를 실행하면 25000 또는 유사 에러(25P02 in_failed_sql_transaction)가 발생하며, 사실상 모든 명령이 거부됩니다. 애플리케이션 레벨에서 예외 처리를 제대로 하지 않고 트랜잭션을 계속 사용하는 경우 이 상황이 매우 빈번하게 발생합니다.
2. 트랜잭션 블록 외부 또는 내부에서 잘못된 트랜잭션 제어 명령 사용
BEGIN 없이 SAVEPOINT를 선언하거나, 이미 활성화된 트랜잭션 블록 내에서 다시 BEGIN을 호출하는 등 트랜잭션 제어 명령을 문맥에 맞지 않게 사용하면 이 에러가 발생할 수 있습니다. 특히 ORM(Object-Relational Mapping) 프레임워크나 커넥션 풀링 환경에서는 트랜잭션 상태가 암묵적으로 관리되기 때문에, 개발자가 트랜잭션 상태를 정확히 파악하지 못한 채 명령을 실행하다 이 에러를 만나는 경우가 많습니다.
3. PL/pgSQL 프로시저 또는 함수 내에서 잘못된 트랜잭션 처리
저장 프로시저(Stored Procedure)나 PL/pgSQL 함수 내부에서 COMMIT 또는 ROLLBACK을 부적절하게 사용하거나, 호출 스택 구조에서 중첩 트랜잭션 처리를 잘못 구성했을 때 이 에러가 발생합니다. PostgreSQL 11 이후 프로시저 내 COMMIT/ROLLBACK이 지원되지만, 함수(function) 내에서는 여전히 트랜잭션 제어가 제한적이며, 이를 혼동하면 invalid transaction state 에러로 이어집니다.
해결 방법
원인 1 해결: 실패한 트랜잭션은 반드시 ROLLBACK 후 재시작
에러가 발생한 트랜잭션은 즉시 ROLLBACK하여 aborted 상태를 해소한 뒤, 필요하다면 새 트랜잭션을 시작해야 합니다.
-- 잘못된 패턴 (aborted 상태에서 계속 쿼리 실행)
BEGIN;
INSERT INTO orders (product_id, qty) VALUES (999, 5); -- 외래키 위반 에러 발생
-- 에러 발생 후에도 계속 쿼리를 날리면 25P02 에러
SELECT * FROM orders; -- ERROR: current transaction is aborted
-- 올바른 패턴
BEGIN;
INSERT INTO orders (product_id, qty) VALUES (999, 5); -- 에러 발생
ROLLBACK; -- 반드시 롤백으로 트랜잭션 정리
-- 새 트랜잭션 시작
BEGIN;
INSERT INTO orders (product_id, qty) VALUES (1, 5); -- 정상 값으로 재시도
COMMIT;
애플리케이션 코드에서는 예외 발생 시 반드시 롤백을 수행하도록 예외 처리를 구성해야 합니다.
-- SAVEPOINT를 활용한 부분 롤백 패턴 (트랜잭션 전체를 롤백하지 않아도 됨)
BEGIN;
INSERT INTO customers (name) VALUES ('Alice');
SAVEPOINT sp1;
INSERT INTO orders (product_id, qty) VALUES (999, 5); -- 에러 발생 가능
-- 에러 발생 시
ROLLBACK TO SAVEPOINT sp1; -- sp1 이후만 롤백
RELEASE SAVEPOINT sp1;
INSERT INTO orders (product_id, qty) VALUES (1, 5); -- 정상 처리
COMMIT;
원인 2 해결: 트랜잭션 상태를 명시적으로 확인하고 제어 명령 사용
현재 트랜잭션 상태를 확인하는 쿼리를 활용하여 불필요한 BEGIN 중복 호출을 방지합니다.
-- 현재 트랜잭션 상태 확인
SELECT current_setting('transaction_isolation');
-- pg_stat_activity를 통해 세션 상태 확인
SELECT pid, state, query, backend_xid
FROM pg_stat_activity
WHERE datname = current_database();
-- 잘못된 패턴: 이미 트랜잭션 블록 안에 있는데 BEGIN 재호출
BEGIN;
SELECT 1;
BEGIN; -- WARNING: 이미 트랜잭션 블록 안에 있음 (중첩 BEGIN 지원 안 됨)
-- 올바른 패턴: 중첩 트랜잭션이 필요하다면 SAVEPOINT 사용
BEGIN;
SAVEPOINT nested_transaction;
INSERT INTO logs (msg) VALUES ('nested operation');
RELEASE SAVEPOINT nested_transaction;
COMMIT;
원인 3 해결: 프로시저와 함수의 트랜잭션 처리 구분
PostgreSQL 11+에서 프로시저(PROCEDURE)는 COMMIT/ROLLBACK이 가능하지만, 함수(FUNCTION)는 불가합니다. 올바른 사용 예는 다음과 같습니다.
-- 잘못된 패턴: 함수 내에서 COMMIT 시도
CREATE OR REPLACE FUNCTION bad_func() RETURNS void AS $$
BEGIN
INSERT INTO logs (msg) VALUES ('test');
COMMIT; -- ERROR: invalid transaction state (함수 내 COMMIT 불가)
END;
$$ LANGUAGE plpgsql;
-- 올바른 패턴: 트랜잭션 제어가 필요하면 PROCEDURE 사용
CREATE OR REPLACE PROCEDURE good_proc() AS $$
BEGIN
INSERT INTO logs (msg) VALUES ('step 1');
COMMIT; -- 프로시저에서는 허용
INSERT INTO logs (msg) VALUES ('step 2');
COMMIT;
END;
$$ LANGUAGE plpgsql;
-- 프로시저 호출 시 CALL 사용
CALL good_proc();
-- PL/pgSQL 내 예외 처리로 안전하게 트랜잭션 관리
CREATE OR REPLACE PROCEDURE safe_proc() AS $$
BEGIN
INSERT INTO orders (product_id, qty) VALUES (1, 10);
EXCEPTION
WHEN foreign_key_violation THEN
RAISE NOTICE '외래키 위반: 롤백 처리';
ROLLBACK;
WHEN OTHERS THEN
RAISE NOTICE '알 수 없는 에러: %', SQLERRM;
ROLLBACK;
END;
$$ LANGUAGE plpgsql;
예방 방법
1. 애플리케이션 레벨에서 철저한 트랜잭션 예외 처리 구현
모든 트랜잭션 블록은 반드시 try-catch (언어별 예외 처리 구문)로 감싸고, 예외 발생 시 무조건 ROLLBACK을 실행하는 패턴을 표준화해야 합니다. 커넥션 풀(PgBouncer, pgpool-II 등) 환경에서는 커넥션 반환 전에 트랜잭션이 항상 정상 종료(COMMIT 또는 ROLLBACK)되었는지 확인하는 로직을 추가하는 것이 중요합니다. 트랜잭션이 aborted 상태로 커넥션 풀에 반환되면, 다음 사용자가 해당 커넥션을 재사용할 때 25000 에러를 만날 수 있습니다.
-- 세션의 트랜잭션 상태 확인 (aborted 여부 감지)
SELECT txid_current_if_assigned(); -- NULL이면 트랜잭션 없음
-- pg_stat_activity에서 idle in transaction (aborted) 감지
SELECT pid, state, wait_event_type, query_start, query
FROM pg_stat_activity
WHERE state = 'idle in transaction (aborted)';
2. idle in transaction 타임아웃 설정으로 좀비 트랜잭션 방지
idle_in_transaction_session_timeout 파라미터를 설정하여 트랜잭션이 장시간 aborted 또는 유휴 상태로 방치되는 것을 자동으로 방지합니다. 이는 invalid transaction state 에러의 2차 피해(다른 세션의 락 대기, 커넥션 낭비 등)를 예방하는 데 매우 효과적입니다.
-- postgresql.conf 또는 세션 레벨 설정
SET idle_in_transaction_session_timeout = '30s'; -- 30초 후 자동 종료
-- 또는 특정 역할(Role)에만 적용
ALTER ROLE app_user SET idle_in_transaction_session_timeout = '60s';
-- 현재 설정 확인
SHOW idle_in_transaction_session_timeout;
관련 에러
- 25P01 (no_active_sql_transaction): 활성 트랜잭션 없이
ROLLBACK또는COMMIT을 호출할 때 발생합니다. - 25P02 (in_failed_sql_transaction): 이미 실패한(aborted) 트랜잭션 내에서 추가 쿼리를 실행할 때 발생하는 에러로,
25000의 가장 흔한 하위 에러입니다. - 25P03 (idle_in_transaction_session_timeout_error):
idle_in_transaction_session_timeout설정 값을 초과하여 트랜잭션이 강제 종료될 때 발생합니다. - 40001 (serialization_failure): 직렬화 격리 수준에서 트랜잭션 충돌 시 발생하며, 재시도 로직 구현 시
25000과 함께 처리해야 하는 에러입니다. - 55000 (object_not_in_prerequisite_state): 객체 상태가 요구 조건을 충족하지 못할 때 발생하며, 트랜잭션 상태 문제와 유사한 맥락에서 접할 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.