PostgreSQL 23503 오류 원인과 해결 방법 완벽 가이드

23503
2026년 08월 25일 | DBMS Error 가이드

이 글에서 다루는 내용

23503 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.

23503 foreign key violation 는?

PostgreSQL 에러 코드 23503은 외래 키(Foreign Key) 제약 조건을 위반했을 때 발생하는 에러입니다. 이는 참조 무결성(Referential Integrity)을 보장하기 위한 데이터베이스의 핵심 보호 장치로, 자식 테이블에 삽입하려는 값이 부모 테이블에 존재하지 않거나, 자식 레코드가 존재하는 상태에서 부모 레코드를 삭제하려 할 때 발생합니다. 실무에서는 대규모 데이터 마이그레이션, 배치 작업, 또는 애플리케이션 레벨의 트랜잭션 순서 오류로 인해 빈번하게 발생합니다.

주요 발생 원인

  • 자식 테이블에 존재하지 않는 부모 키를 참조하는 INSERT/UPDATE

가장 흔한 원인으로, 자식 테이블에 데이터를 삽입할 때 참조하는 부모 테이블의 키 값이 아직 존재하지 않는 경우입니다. 예를 들어, orders 테이블에 주문을 INSERT할 때 해당 customer_idcustomers 테이블에 없는 경우 이 에러가 발생합니다. 애플리케이션에서 INSERT 순서를 잘못 설계했거나, 사용자가 잘못된 ID 값을 입력했을 때 자주 나타납니다.

  • 자식 레코드가 존재하는 부모 레코드를 삭제(DELETE)하려는 경우

부모 테이블의 레코드를 삭제하려 할 때, 해당 레코드를 참조하는 자식 테이블의 데이터가 남아 있으면 이 에러가 발생합니다. 예를 들어 customers 테이블의 특정 고객을 삭제하려 할 때 orders 테이블에 해당 고객의 주문 내역이 남아 있는 경우입니다. 이는 데이터 정리 작업이나 레거시 데이터 마이그레이션 중 매우 자주 발생하는 패턴입니다.

  • 데이터 마이그레이션 또는 대량 적재 시 순서 오류

대량의 데이터를 여러 테이블에 동시에 적재하거나 마이그레이션할 때, 부모 테이블보다 자식 테이블이 먼저 INSERT되는 경우 이 에러가 발생합니다. ETL 파이프라인, 데이터 웨어하우스 적재, 또는 COPY 명령어를 활용한 대량 데이터 이관 작업에서 테이블 간 의존 순서를 고려하지 않으면 반드시 이 문제를 만나게 됩니다.

해결 방법

원인 1 해결: 부모 데이터 존재 여부 확인 후 INSERT

먼저 참조하려는 부모 키가 실제로 존재하는지 확인하고, 없다면 부모 데이터를 먼저 삽입합니다.

-- 에러 발생 예시
INSERT INTO orders (order_id, customer_id, amount)
VALUES (1001, 9999, 50000);
-- ERROR: insert or update on table "orders" violates foreign key constraint
-- DETAIL: Key (customer_id)=(9999) is not present in table "customers".

-- 해결 방법 1: 부모 데이터 존재 확인
SELECT id FROM customers WHERE id = 9999;

-- 해결 방법 2: 부모 데이터가 없으면 먼저 삽입
INSERT INTO customers (id, name, email)
VALUES (9999, '홍길동', 'hong@example.com')
ON CONFLICT (id) DO NOTHING;

-- 이후 자식 데이터 삽입
INSERT INTO orders (order_id, customer_id, amount)
VALUES (1001, 9999, 50000);

-- 해결 방법 3: 트랜잭션으로 묶어서 안전하게 처리
BEGIN;
  INSERT INTO customers (id, name, email)
  VALUES (9999, '홍길동', 'hong@example.com');
  
  INSERT INTO orders (order_id, customer_id, amount)
  VALUES (1001, 9999, 50000);
COMMIT;

원인 2 해결: 자식 레코드 먼저 삭제 후 부모 삭제

부모 레코드를 삭제하기 전에 자식 레코드를 먼저 처리해야 합니다.

-- 에러 발생 예시
DELETE FROM customers WHERE id = 9999;
-- ERROR: update or delete on table "customers" violates foreign key constraint
-- DETAIL: Key (id)=(9999) is still referenced from table "orders".

-- 해결 방법 1: 자식 레코드 먼저 삭제
BEGIN;
  DELETE FROM orders WHERE customer_id = 9999;
  DELETE FROM customers WHERE id = 9999;
COMMIT;

-- 해결 방법 2: ON DELETE CASCADE 옵션 활용 (스키마 수정이 가능한 경우)
-- 기존 외래 키 제약 조건 삭제 후 CASCADE 옵션으로 재생성
ALTER TABLE orders
  DROP CONSTRAINT orders_customer_id_fkey;

ALTER TABLE orders
  ADD CONSTRAINT orders_customer_id_fkey
  FOREIGN KEY (customer_id)
  REFERENCES customers(id)
  ON DELETE CASCADE;

-- 이제 부모 삭제 시 자식도 자동 삭제됨
DELETE FROM customers WHERE id = 9999;

-- 해결 방법 3: SET NULL 옵션 (자식을 남기되 참조를 NULL로 처리)
ALTER TABLE orders
  ADD CONSTRAINT orders_customer_id_fkey
  FOREIGN KEY (customer_id)
  REFERENCES customers(id)
  ON DELETE SET NULL;

원인 3 해결: 마이그레이션 시 외래 키 제약 조건 일시 비활성화

대량 데이터 적재 시 외래 키 제약을 일시적으로 비활성화하고, 적재 완료 후 다시 활성화합니다.

-- 방법 1: 세션 레벨에서 트리거 비활성화 (슈퍼유저 권한 필요)
SET session_replication_role = 'replica';

-- 대량 데이터 적재
COPY customers FROM '/data/customers.csv' CSV HEADER;
COPY orders FROM '/data/orders.csv' CSV HEADER;

-- 다시 활성화
SET session_replication_role = 'DEFAULT';

-- 방법 2: 특정 테이블의 외래 키만 비활성화
ALTER TABLE orders DISABLE TRIGGER ALL;
-- 데이터 적재
COPY orders FROM '/data/orders.csv' CSV HEADER;
-- 다시 활성화
ALTER TABLE orders ENABLE TRIGGER ALL;

-- 방법 3: 데이터 적재 후 무결성 검증
-- 적재 후 참조 무결성을 수동으로 확인
SELECT o.order_id, o.customer_id
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.id
WHERE c.id IS NULL;
-- 결과가 없으면 데이터 무결성 확인 완료

예방 방법

  • 애플리케이션 레벨에서 INSERT/DELETE 순서를 명확히 정의하고 트랜잭션으로 묶기

외래 키 관계가 있는 테이블에 데이터를 삽입하거나 삭제할 때는 반드시 부모 → 자식 순서(삽입 시) 또는 자식 → 부모 순서(삭제 시)를 코드 레벨에서 강제하고, 전체 작업을 하나의 트랜잭션으로 묶어야 합니다. 또한 마이그레이션 스크립트나 ETL 파이프라인을 개발할 때는 테이블 간의 의존성 그래프를 미리 그려 놓고, 위상 정렬(Topological Sort) 순서에 따라 작업 순서를 결정하는 것이 좋습니다.

-- 좋은 예: 트랜잭션과 올바른 순서 보장
BEGIN;
  -- 1. 부모 테이블 먼저
  INSERT INTO departments (id, name) VALUES (10, '개발팀');
  -- 2. 자식 테이블 나중에
  INSERT INTO employees (id, name, dept_id) VALUES (1, '김철수', 10);
COMMIT;
  • 외래 키 제약 조건에 적절한 ON DELETE / ON UPDATE 정책 설정

테이블 설계 단계에서 비즈니스 요구사항에 맞는 참조 동작(Referential Action)을 미리 정의해두면 런타임 에러를 상당 부분 예방할 수 있습니다. ON DELETE CASCADE, ON DELETE SET NULL, ON DELETE RESTRICT 중 비즈니스 로직에 맞는 옵션을 선택하고, 주석이나 문서에 그 이유를 명확히 기록해 두는 것을 권장합니다.

-- 설계 단계에서 정책 명확히 정의
CREATE TABLE orders (
    order_id    SERIAL PRIMARY KEY,
    customer_id INT NOT NULL,
    amount      NUMERIC(10,2),
    CONSTRAINT orders_customer_id_fkey
        FOREIGN KEY (customer_id)
        REFERENCES customers(id)
        ON DELETE RESTRICT    -- 고객 삭제 시 주문이 있으면 삭제 불가
        ON UPDATE CASCADE     -- 고객 ID 변경 시 주문에도 자동 반영
);

관련 에러

  • 23000 (integrity_constraint_violation): 23503의 상위 에러 클래스로, 모든 무결성 제약 위반을 포괄합니다.
  • 23502 (not_null_violation): NOT NULL 제약 조건 위반으로, 필수 컬럼에 NULL 값을 삽입할 때 발생합니다.
  • 23505 (unique_violation): UNIQUE 또는 PRIMARY KEY 제약 조건 위반으로, 중복 키 삽입 시 발생하며 외래 키와 함께 자주 연쇄 발생합니다.
  • 42830 (invalid_foreign_key): 외래 키 정의 자체가 잘못된 경우 발생하며, 참조 대상 컬럼이 UNIQUE 또는 PRIMARY KEY가 아닐 때 나타납니다.

DBMS 에러 코드 시리즈

주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.

본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.

댓글 남기기