2026년 06월 24일 | DBMS Error 가이드
이 글에서 다루는 내용
25007 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
25007 schema and data statement mixing not supported 는?
PostgreSQL 에러 코드 25007은 단일 트랜잭션 블록 내에서 DDL(Data Definition Language) 문과 DML(Data Manipulation Language) 문을 특정 컨텍스트에서 혼합하여 사용할 때 발생하는 오류입니다. 주로 CREATE TABLE AS SELECT, 트리거, 또는 특정 확장 모듈이나 외부 데이터 래퍼(FDW) 환경에서 스키마 변경 작업과 데이터 조작 작업이 동일한 트랜잭션 내에서 충돌할 때 나타납니다. 특히 PostgreSQL의 논리적 복제(Logical Replication) 또는 일부 서드파티 확장 기능(예: pg_logical, dblink)과 함께 사용할 때 이 에러가 빈번하게 발생하며, 데이터베이스의 일관성을 보호하기 위해 엔진 레벨에서 강제로 차단됩니다.
주요 발생 원인
1. 논리적 복제(Logical Replication) 환경에서의 혼합 트랜잭션
논리적 복제 슬롯이 활성화된 상태에서 하나의 트랜잭션 안에 DDL과 DML을 함께 실행하려 할 때 이 에러가 가장 빈번하게 발생합니다. 논리적 복제 디코더는 스키마 변경과 데이터 변경을 별도의 이벤트로 처리해야 하기 때문에, 이 둘을 동일한 트랜잭션에 묶으면 내부 처리 로직이 충돌하게 됩니다. 특히 wal2json, pgoutput 등의 출력 플러그인 사용 시 이 제약이 더욱 엄격하게 적용됩니다.
2. 외부 데이터 래퍼(FDW) 또는 확장 모듈 내부 제약
postgres_fdw, oracle_fdw 등 외부 데이터 래퍼를 사용하는 환경에서 외부 테이블의 스키마를 수정하는 동시에 데이터를 읽거나 쓰는 작업을 하나의 트랜잭션으로 묶을 경우 이 에러가 발생할 수 있습니다. FDW는 트랜잭션 경계를 내부적으로 관리하며, 스키마 변경이 외부 서버에 즉시 반영되지 않기 때문에 데이터 접근 시 모순이 생깁니다. 이로 인해 PostgreSQL 엔진이 해당 혼합 작업을 허용하지 않고 에러를 발생시킵니다.
3. PL/pgSQL 또는 저장 프로시저 내에서의 동적 DDL과 DML 혼용
저장 프로시저나 PL/pgSQL 함수 내에서 EXECUTE 구문을 이용해 동적으로 테이블을 생성하고, 같은 트랜잭션 블록 내에서 바로 해당 테이블에 데이터를 삽입하거나 조회하는 패턴이 문제가 됩니다. 일반적인 PostgreSQL 환경에서는 이 패턴이 허용되지만, 특정 확장 기능이나 복제 설정이 활성화된 경우 엔진이 이 혼합을 감지하고 차단합니다. 함수 내부의 트랜잭션 경계를 명확히 구분하지 않으면 예기치 않은 에러로 이어질 수 있습니다.
해결 방법
원인 1 해결: DDL과 DML 트랜잭션 분리
가장 근본적인 해결책은 DDL 문과 DML 문을 별도의 트랜잭션으로 분리하는 것입니다.
-- ❌ 잘못된 방법: 하나의 트랜잭션에 DDL과 DML 혼합
BEGIN;
CREATE TABLE orders_2024 (
order_id SERIAL PRIMARY KEY,
customer_id INT NOT NULL,
amount NUMERIC(10,2),
created_at TIMESTAMPTZ DEFAULT NOW()
);
INSERT INTO orders_2024 (customer_id, amount) VALUES (1, 150.00);
COMMIT;
-- ✅ 올바른 방법: 트랜잭션 분리
-- 1단계: DDL 트랜잭션
BEGIN;
CREATE TABLE orders_2024 (
order_id SERIAL PRIMARY KEY,
customer_id INT NOT NULL,
amount NUMERIC(10,2),
created_at TIMESTAMPTZ DEFAULT NOW()
);
COMMIT;
-- 2단계: DML 트랜잭션 (DDL 커밋 후 별도 실행)
BEGIN;
INSERT INTO orders_2024 (customer_id, amount) VALUES (1, 150.00);
INSERT INTO orders_2024 (customer_id, amount) VALUES (2, 200.00);
COMMIT;
원인 2 해결: FDW 환경에서의 세션 분리
FDW를 사용하는 환경에서는 스키마 변경 작업을 완전히 커밋한 뒤 새로운 세션 또는 트랜잭션에서 데이터 작업을 수행해야 합니다.
-- ❌ 잘못된 방법: FDW 테이블 스키마 변경과 데이터 조회 혼합
BEGIN;
-- 외부 테이블 옵션 변경 (스키마 관련 작업)
ALTER FOREIGN TABLE remote_sales OPTIONS (SET schema_name 'public');
-- 같은 트랜잭션에서 데이터 조회 시도 → 에러 발생
SELECT * FROM remote_sales WHERE sale_date = CURRENT_DATE;
COMMIT;
-- ✅ 올바른 방법: 작업 분리
-- 1단계: 스키마 변경 먼저 커밋
BEGIN;
ALTER FOREIGN TABLE remote_sales OPTIONS (SET schema_name 'public');
COMMIT;
-- 2단계: 새 트랜잭션에서 데이터 조회
BEGIN;
SELECT * FROM remote_sales WHERE sale_date = CURRENT_DATE;
COMMIT;
원인 3 해결: PL/pgSQL 프로시저에서 COMMIT 명시적 사용
PostgreSQL 11 이상에서는 저장 프로시저 내에서 중간 COMMIT을 사용할 수 있습니다. DDL 실행 후 명시적으로 커밋하여 새로운 트랜잭션을 시작합니다.
-- ❌ 잘못된 방법: 프로시저 내 DDL/DML 혼용 (복제 활성 환경)
CREATE OR REPLACE PROCEDURE create_and_populate_log()
LANGUAGE plpgsql
AS $$
BEGIN
-- DDL 실행
EXECUTE 'CREATE TABLE IF NOT EXISTS app_log_' ||
TO_CHAR(CURRENT_DATE, 'YYYYMMDD') ||
' (log_id SERIAL, message TEXT, logged_at TIMESTAMPTZ DEFAULT NOW())';
-- 같은 트랜잭션에서 DML 실행 → 에러 발생 가능
EXECUTE 'INSERT INTO app_log_' ||
TO_CHAR(CURRENT_DATE, 'YYYYMMDD') ||
' (message) VALUES ($1)'
USING 'Procedure started';
END;
$$;
-- ✅ 올바른 방법: 명시적 COMMIT으로 트랜잭션 분리 (PostgreSQL 11+)
CREATE OR REPLACE PROCEDURE create_and_populate_log()
LANGUAGE plpgsql
AS $$
BEGIN
-- DDL 실행
EXECUTE 'CREATE TABLE IF NOT EXISTS app_log_' ||
TO_CHAR(CURRENT_DATE, 'YYYYMMDD') ||
' (log_id SERIAL, message TEXT, logged_at TIMESTAMPTZ DEFAULT NOW())';
-- DDL 후 명시적 커밋으로 트랜잭션 경계 설정
COMMIT;
-- 새 트랜잭션에서 DML 실행
EXECUTE 'INSERT INTO app_log_' ||
TO_CHAR(CURRENT_DATE, 'YYYYMMDD') ||
' (message) VALUES ($1)'
USING 'Procedure started';
COMMIT;
END;
$$;
-- 프로시저 호출 (CALL 사용, BEGIN/COMMIT 블록 외부에서 실행)
CALL create_and_populate_log();
논리 복제 환경에서의 추가 확인 방법
-- 현재 활성화된 복제 슬롯 확인
SELECT slot_name, plugin, slot_type, active, confirmed_flush_lsn
FROM pg_replication_slots
WHERE active = true;
-- 현재 세션의 트랜잭션 격리 수준 및 복제 역할 확인
SHOW transaction_isolation;
SELECT current_setting('wal_level');
-- 복제 관련 설정 확인
SELECT name, setting
FROM pg_settings
WHERE name IN ('wal_level', 'max_replication_slots', 'max_wal_senders');
예방 방법
1. 마이그레이션 스크립트에서 DDL/DML 트랜잭션 명시적 분리 원칙 수립
팀 차원의 마이그레이션 가이드라인을 수립하여 DDL 작업과 DML 작업을 반드시 별개의 트랜잭션 블록으로 분리하도록 코딩 컨벤션을 정의합니다. Flyway, Liquibase 같은 DB 마이그레이션 도구를 사용할 경우, 각 마이그레이션 파일에 DDL과 DML을 혼합하지 않도록 파일 분리 정책을 적용하는 것이 좋습니다. CI/CD 파이프라인에 정적 SQL 분석 도구(예: sqlfluff)를 통합하여 커밋 전에 혼합 패턴을 자동으로 감지하는 게이트를 구축하면 운영 환경에서의 에러 발생을 사전에 차단할 수 있습니다.
2. 논리 복제 또는 FDW 활성 환경에서의 테스트 환경 구성
운영 환경과 동일한 복제 슬롯 및 FDW 설정을 갖춘 스테이징 환경을 구축하고, 모든 스키마 변경 스크립트를 반드시 이 환경에서 사전 테스트합니다. 특히 논리 복제가 활성화된 환경에서는 pg_logical_emit_message() 등의 함수를 활용해 트랜잭션 경계를 명확히 추적하는 모니터링 체계를 갖추는 것이 장기적으로 안정적인 운영에 도움이 됩니다.
관련 에러
- 25000 –
in_failed_sql_transaction: 이미 오류가 발생한 트랜잭션 내에서 추가 SQL 문을 실행하려 할 때 발생하며, 25007과 마찬가지로 트랜잭션 상태 관리 실패와 관련됩니다. - 25001 –
active_sql_transaction: DDL 문 중 일부(예:CREATE DATABASE,CREATE TABLESPACE)가 활성 트랜잭션 내에서 실행될 때 발생합니다. 25007과 유사한 트랜잭션 컨텍스트 충돌 에러입니다. - 25P01 –
no_active_sql_transaction: 트랜잭션 없이ROLLBACK또는COMMIT을 호출할 때 발생하며, 트랜잭션 경계 관리 실수로 인해 25007과 함께 연쇄적으로 나타나는 경우가 있습니다. - 0A000 –
feature_not_supported: 특정 확장 기능이나 FDW가 해당 혼합 작업을 지원하지 않을 때 25007 대신 이 에러가 발생하기도 합니다. 두 에러 모두 확인하여 근본 원인을 추적하는 것이 중요합니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.