2026년 06월 22일 | DBMS Error 가이드
이 글에서 다루는 내용
25000 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
25000 invalid transaction state 는?
PostgreSQL 에러 코드 25000 (invalid_transaction_state) 은 트랜잭션이 현재 상태에서 허용되지 않는 명령 또는 작업을 실행하려 할 때 발생합니다. 예를 들어, 트랜잭션이 이미 오류로 인해 중단(aborted) 상태가 되었음에도 불구하고 추가적인 SQL 명령을 계속 실행하거나, 트랜잭션 블록 내부에서 허용되지 않는 특수한 명령을 수행할 때 이 에러가 나타납니다. 이 에러는 애플리케이션의 트랜잭션 관리 로직이 정확하지 않거나, 연결 풀링 환경에서 트랜잭션 상태가 제대로 정리되지 않은 채 재사용될 때 빈번하게 발생합니다.
주요 발생 원인
- 이미 중단(Aborted)된 트랜잭션에서 쿼리 실행
PostgreSQL에서는 트랜잭션 블록 내부에서 오류가 발생하면, 해당 트랜잭션은 즉시 “aborted” 상태로 전환됩니다. 이 상태에서 ROLLBACK 없이 추가 SQL 명령을 실행하면 ERROR: current transaction is aborted, commands ignored until end of transaction block 또는 25000 계열의 에러가 발생하며, 트랜잭션이 유효하지 않은 상태임을 알립니다.
“`sql
BEGIN;
SELECT 1 / 0; — 오류 발생: division by zero (트랜잭션 aborted 상태 전환)
SELECT * FROM users; — 25000 에러 발생: invalid transaction state
ROLLBACK; — 반드시 ROLLBACK으로 트랜잭션 정리 필요
“`
- 트랜잭션 블록 내에서 허용되지 않는 명령 실행
일부 PostgreSQL 명령은 트랜잭션 블록 내부에서 실행될 수 없습니다. 예를 들어 VACUUM, CREATE DATABASE, DROP DATABASE, CLUSTER 같은 명령은 명시적인 트랜잭션 블록 안에서 호출하면 에러가 발생합니다. 이는 해당 명령들이 내부적으로 자체 트랜잭션 관리 방식을 사용하거나, 전체 데이터베이스 수준의 잠금이 필요하기 때문입니다.
“`sql
BEGIN;
INSERT INTO logs (message) VALUES (‘작업 시작’);
VACUUM users; — 에러: VACUUM cannot run inside a transaction block
COMMIT;
— 올바른 방법: VACUUM은 트랜잭션 외부에서 단독 실행
INSERT INTO logs (message) VALUES (‘작업 시작’);
COMMIT;
VACUUM users;
“`
- 연결 풀링 환경에서 트랜잭션 상태 미정리
PgBouncer, HikariCP 등의 연결 풀을 사용하는 환경에서, 애플리케이션이 트랜잭션을 완전히 커밋 또는 롤백하지 않고 연결을 반환하면, 다음 요청에서 해당 연결을 재사용할 때 잘못된 트랜잭션 상태가 남아 있어 25000 에러가 발생할 수 있습니다. 특히 예외 처리 로직이 미흡한 애플리케이션 코드에서 트랜잭션을 열고 오류 발생 시 연결만 반환하는 경우가 대표적인 원인입니다.
“`sql
— 연결 풀 반환 전 트랜잭션 상태 확인 방법
SELECT txid_current_if_assigned(); — NULL이면 활성 트랜잭션 없음
SELECT pg_current_xact_id_if_assigned(); — PostgreSQL 13 이상
— 애플리케이션 레벨에서 항상 명시적으로 처리
BEGIN;
— … 작업 수행 …
— 오류 발생 시
ROLLBACK;
— 정상 완료 시
COMMIT;
“`
해결 방법
원인 1: Aborted 트랜잭션 정리
중단된 트랜잭션에서 이 에러가 발생하면, 반드시 ROLLBACK 명령으로 트랜잭션을 종료한 후 다시 시작해야 합니다. SAVEPOINT를 활용하면 전체 트랜잭션을 롤백하지 않고 특정 지점으로만 되돌릴 수 있어, 복잡한 트랜잭션에서 유용합니다.
-- SAVEPOINT를 활용한 부분 롤백
BEGIN;
INSERT INTO orders (user_id, amount) VALUES (1, 5000);
SAVEPOINT before_discount;
UPDATE coupons SET used = true WHERE coupon_id = 999;
-- 위 쿼리에서 오류 발생 시:
ROLLBACK TO SAVEPOINT before_discount;
-- 쿠폰 업데이트 없이 주문만 커밋
COMMIT;
원인 2: 트랜잭션 외부에서 명령 실행
트랜잭션 블록 외부에서만 실행 가능한 명령은 반드시 COMMIT 또는 ROLLBACK 이후에 독립적으로 실행해야 합니다. 아래는 대표적인 트랜잭션 블록 불허 명령 목록과 올바른 사용법입니다.
-- 잘못된 방법
BEGIN;
CREATE DATABASE new_project_db; -- 에러 발생
COMMIT;
-- 올바른 방법: 트랜잭션 블록 외부에서 실행
CREATE DATABASE new_project_db;
-- VACUUM 올바른 실행 예시
-- psql 세션에서 직접 실행 (트랜잭션 없이)
VACUUM (VERBOSE, ANALYZE) large_table;
-- 자동화 스크립트에서의 예시 (Python psycopg2)
-- conn.autocommit = True 로 설정 후 VACUUM 실행
원인 3: 연결 풀 환경에서의 상태 초기화
PgBouncer의 server_reset_query 설정이나 애플리케이션 레벨의 연결 검증 로직을 통해 연결 반환 전 트랜잭션 상태를 항상 초기화해야 합니다.
-- PgBouncer pgbouncer.ini 설정 예시
-- server_reset_query = DISCARD ALL
-- 연결 상태 점검 쿼리 (DBA 모니터링용)
SELECT pid, state, query, backend_xid, backend_xmin
FROM pg_stat_activity
WHERE state IN ('idle in transaction', 'idle in transaction (aborted)')
ORDER BY query_start;
-- 오래된 idle in transaction 세션 강제 종료
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
AND query_start < NOW() - INTERVAL '10 minutes';
예방 방법
- 명시적 트랜잭션 관리 및 예외 처리 코드 적용
애플리케이션 코드에서 반드시 BEGIN ~ COMMIT/ROLLBACK 쌍을 보장하는 구조를 사용하고, 예외 발생 시 ROLLBACK이 항상 실행되도록 try/catch/finally 패턴 또는 ORM의 트랜잭션 관리 기능을 활용하세요. PostgreSQL의 PL/pgSQL 프로시저를 사용하는 경우에도 EXCEPTION 블록을 반드시 포함시켜 트랜잭션이 유효한 상태에서 종료되도록 설계합니다.
“`sql
— PL/pgSQL에서의 안전한 트랜잭션 처리 예시
DO $$
BEGIN
INSERT INTO audit_log (action) VALUES (‘START’);
UPDATE accounts SET balance = balance – 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
INSERT INTO audit_log (action) VALUES (‘END’);
EXCEPTION
WHEN OTHERS THEN
RAISE WARNING ‘트랜잭션 오류 발생: %’, SQLERRM;
ROLLBACK;
END;
$$;
“`
idle in transaction세션에 대한 타임아웃 설정
idle_in_transaction_session_timeout 파라미터를 설정하여 트랜잭션이 일정 시간 이상 열려 있으면 자동으로 종료되도록 합니다. 이를 통해 연결 풀 고갈, 잠금 경쟁, 그리고 25000 에러의 원인이 되는 미정리 트랜잭션 상태를 예방할 수 있습니다.
“`sql
— postgresql.conf 또는 세션 레벨 설정
— 전역 설정 (postgresql.conf)
— idle_in_transaction_session_timeout = ‘5min’
— 세션 레벨 설정
SET idle_in_transaction_session_timeout = ‘300000’; — 5분 (ms 단위)
— 특정 사용자/데이터베이스에 적용
ALTER ROLE app_user SET idle_in_transaction_session_timeout = ‘120000’;
ALTER DATABASE myapp SET idle_in_transaction_session_timeout = ‘300000’;
— 현재 설정 확인
SHOW idle_in_transaction_session_timeout;
“`
관련 에러
- 25001 (active_sql_transaction): 트랜잭션이 이미 활성화된 상태에서 트랜잭션을 시작할 수 없는 명령을 실행할 때 발생하며, 25000의 하위 에러 코드입니다.
- 25002 (branch_transaction_already_active): 분산 트랜잭션(XA 트랜잭션) 환경에서 브랜치 트랜잭션이 이미 활성화된 경우 발생합니다.
- 25P01 (no_active_sql_transaction):
ROLLBACK이나COMMIT을 활성 트랜잭션 없이 호출할 때 발생하는 경고성 에러로, 트랜잭션 관리 로직의 불일치를 나타냅니다. - 25P02 (in_failed_sql_transaction): 실패한 트랜잭션 블록 내에서 명령을 계속 실행하려 할 때 발생하며, 25000과 가장 밀접하게 관련된 에러입니다.
ROLLBACK없이 오류 후 쿼리를 실행하면 이 에러가 반환됩니다. - 40001 (serialization_failure): 직렬화 격리 수준에서 트랜잭션 충돌 발생 시 나타나며, 트랜잭션 재시도 로직과 함께 25000 에러와 복합적으로 발생할 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.