PostgreSQL 18의 UNIQUE DEFERRABLE INITIALLY DEFERRED는 중간 문장 뒤의 일시적인 중복을 허용하고, 트랜잭션이 끝날 때 고유성을 검사합니다. 최종 상태에 중복이 남아 있으면 COMMIT에서 실패합니다.

기본 NOT DEFERRABLE UNIQUE는 행을 삽입·갱신할 때 즉시 검사합니다. DEFERRABLE INITIALLY IMMEDIATE는 문장 끝, DEFERRABLE INITIALLY DEFERRED는 트랜잭션 끝에 검사하므로 세 시점을 구분해야 합니다.

제약 검사를 미루어도 최종 UNIQUE 조건은 사라지지 않습니다

UNIQUE는 지정한 열 또는 열 조합의 값이 행 사이에서 중복되지 않게 합니다. 기본 NOT DEFERRABLE UNIQUE는 각 행의 삽입·갱신 시 즉시 검사하므로 한 번의 트랜잭션 안에서 A와 B를 서로 교환할 때 첫 번째 갱신이 아직 존재하는 B와 충돌할 수 있습니다. 이때 필요한 기능은 중복을 영구적으로 허용하는 옵션이 아니라, 제약 검사를 트랜잭션의 뒤로 미루는 기능입니다. PostgreSQL 18에서 DEFERRABLE INITIALLY DEFERRED로 선언하면 해당 제약은 트랜잭션 끝에서 검사됩니다. 중간에는 잠시 중복 상태가 생길 수 있지만, COMMIT 시점에는 모든 행이 고유해야 합니다. 검사를 미룬다는 사실만 보고 ‘중복을 허용한다’고 답하면 안 됩니다. 최종 상태에서 제약을 만족해야 트랜잭션을 완료할 수 있습니다.

두 고유 레이블을 교환하는 PostgreSQL 18 교육용 트랜잭션입니다. 실제 서버에서 실행한 예제가 아닙니다.
CREATE TABLE queue_slot (
    id integer PRIMARY KEY,
    label text UNIQUE DEFERRABLE INITIALLY DEFERRED
);

INSERT INTO queue_slot (id, label) VALUES (1, 'A'), (2, 'B');

BEGIN;
UPDATE queue_slot SET label = 'B' WHERE id = 1;
UPDATE queue_slot SET label = 'A' WHERE id = 2;
COMMIT;
검사 시점이 달라도 트랜잭션 종료 시 두 레이블은 여전히 고유해야 합니다
시점id=1id=2중복 여부지연 제약의 판정
시작AB없음통과
첫 UPDATE 뒤BB있음아직 검사하지 않음
둘째 UPDATE 뒤BA없음최종 상태가 고유
COMMITBA없음커밋 가능

행 id를 고정해 어느 값을 바꾸는지 놓치지 않습니다

예제의 id는 행을 안정적으로 구분합니다. 먼저 id 1의 레이블 A를 B로 바꾸면 일시적으로 두 행이 모두 B가 됩니다. 기본 NOT DEFERRABLE UNIQUE라면 id 1 행을 갱신할 때 즉시 UNIQUE 위반이 발생합니다. 지연 검사가 활성화되어 있으면 다음 문장까지 진행해 id 2의 B를 A로 바꿀 수 있습니다. 최종 상태는 id 1이 B, id 2가 A라 중복이 없습니다. 이 시점에 COMMIT하면 제약을 만족합니다. 반대로 둘째 UPDATE를 빼고 COMMIT하면 두 행이 B인 상태가 남습니다. 그때는 제약을 더 이상 미룰 시점이 없으므로 트랜잭션을 완료할 수 없습니다. 이 결과는 중간 상태가 아니라 검사 시점의 최종 상태가 올바른지를 기준으로 결정됩니다. 표에 각 행의 id까지 적어 두면 첫 UPDATE가 어떤 행을 바꿨는지 놓치지 않습니다.

제약 정의, 검사 시점, 최종 행 상태를 차례로 봅니다

  1. 제약이 UNIQUE인지, 어떤 열이나 열 조합에 적용되는지 확인합니다.
  2. DEFERRABLE인지 확인합니다. NOT DEFERRABLE이면 검사 시점을 뒤로 미룰 수 없습니다.
  3. INITIALLY DEFERRED인지 또는 트랜잭션에서 SET CONSTRAINTS ... DEFERRED를 적용했는지 확인합니다.
  4. 각 UPDATE 뒤의 중간 값과 트랜잭션 마지막의 값을 별도로 적습니다.
  5. COMMIT 때 중복이 남아 있는지 확인하고 완료 가능 여부를 판단합니다.

DEFERRABLE INITIALLY IMMEDIATE도 가능한 정의입니다. 이 경우 처음에는 각 문장이 끝날 때 검사하지만, 트랜잭션 안에서 SET CONSTRAINTS constraint_name DEFERRED로 검사 시점을 바꿀 수 있습니다. 반대로 SET CONSTRAINTS ... IMMEDIATE로 바꾸면 현재까지 쌓인 위반을 그 시점에 검사합니다. 따라서 문제에서 초기 설정과 SET CONSTRAINTS 명령을 모두 읽어야 합니다. 기본 NOT DEFERRABLE UNIQUE의 행별 즉시 검사와 이 문장 끝 검사는 다릅니다. 하나의 명령이 여러 고유 키를 갱신하면 차이가 나타날 수 있습니다. 단순히 정의에 DEFERRABLE이 있다고 해서 COMMIT까지 자동으로 늦춰진다고 보면 안 됩니다.

CHECK와 NOT NULL은 같은 방식으로 미룰 수 없습니다

PostgreSQL 18은 UNIQUE, PRIMARY KEY, EXCLUDE, REFERENCES 제약에 DEFERRABLE을 허용하지만 CHECK와 NOT NULL은 지연할 수 없다고 설명합니다. 제약 종류를 잘못 구분하면 검사 시점을 임의로 바꾸어 답을 만들게 됩니다. 또한 지연 가능한 제약은 INSERT ... ON CONFLICT의 충돌 판정 기준으로 사용할 수 없으므로, 중복 입력 문제에서 UPSERT 동작까지 같은 기능으로 기대하지 마세요. 이 글의 SQL은 문서에서 확인한 제약 시점과 행 값을 수동으로 추적한 예시입니다. 실제 PostgreSQL 서버에 연결하거나 실행하지 않았습니다. 운영 테이블에서 제약을 바꾸는 일은 데이터와 응용 프로그램 흐름에 영향을 주므로, 이 설명만으로 실제 스키마 변경을 권하지 않습니다.

정답을 가리고 확인해 보세요

이 글을 위해 만든 학습 예시이며 공식 기출문제나 판매 카드의 인용이 아닙니다.

PostgreSQL 18에서 지연 가능한 UNIQUE를 사용해 A/B 레이블을 교환합니다. 첫 UPDATE 뒤 중복이 생겼다가 둘째 UPDATE 뒤 사라지면 COMMIT할 수 있나요? 최종 중복이 남으면 어떻게 되나요?

정답과 해설 확인DEFERRABLE INITIALLY DEFERRED이면 첫 UPDATE 뒤의 중복은 당장 검사하지 않고 둘째 UPDATE로 최종 고유 상태를 만들면 COMMIT할 수 있습니다. COMMIT 시 중복이 남으면 제약 위반으로 완료할 수 없습니다.

기본 NOT DEFERRABLE UNIQUE는 행 갱신 시 즉시, DEFERRABLE INITIALLY IMMEDIATE는 문장 끝, DEFERRABLE INITIALLY DEFERRED는 트랜잭션 끝에 검사합니다. SET CONSTRAINTS로 지연 모드를 바꾸면 그 시점의 소급 검사도 확인해야 합니다.