ON DELETE CASCADE는 삭제된 부모 행을 참조하는 자식 행을 함께 삭제합니다. ON DELETE SET NULL은 자식 행을 남겨 두고 외래 키 열을 NULL로 바꿉니다. 이름만 외우기보다 삭제 대상과 영향을 받는 행을 표시하면 혼동이 줄어듭니다.

아래 결과는 PostgreSQL 18의 단순한 외래 키와 명시한 데이터에 대한 설명입니다. 다른 트리거나 추가 제약은 없다고 가정하고, 동작 하나씩 독립적으로 비교합니다.

화살표는 학생에서 학급으로 향한다

학급 테이블 class_room의 키는 id, 학생 테이블 pupil의 키도 id라고 하겠습니다. 학생의 class_id가 학급 id를 참조하므로 외래 키는 학생 쪽에 있습니다. '부모'는 먼저 만들어진 테이블이라는 뜻이 아니라 여기서 참조되는 쪽이라는 뜻입니다. 다음 이름과 번호는 설명을 위해 만든 데이터입니다.

삭제 전 학습용 학생 표
학생 id이름class_id
101가온10
102나래10
103다온20
PostgreSQL용 창작 연습 데이터: SET NULL 사례
CREATE TABLE class_room (id integer PRIMARY KEY);
CREATE TABLE pupil (
  id integer PRIMARY KEY,
  name text,
  class_id integer REFERENCES class_room(id)
    ON DELETE SET NULL
);
INSERT INTO class_room VALUES (10), (20);
INSERT INTO pupil VALUES
  (101, '가온', 10), (102, '나래', 10), (103, '다온', 20);

지금 하려는 작업은 학급 10을 삭제하는 것입니다. 학급 20이나 학생 103은 직접 대상이 아닙니다. pupil의 행을 먼저 삭제하는 문제라면 같은 ON DELETE 절을 반대 방향으로 적용하면 안 됩니다. 참조하는 학생이 없어지는 것과 참조되는 학급이 없어지는 것은 다른 출발점입니다.

같은 삭제문을 세 가지 설정에서 비교하기

연습 DB에서만 실행할 삭제와 결과 확인
DELETE FROM class_room WHERE id = 10;
SELECT id, name, class_id FROM pupil ORDER BY id;
각 설정을 원래 데이터에서 따로 적용한 결과
설정학급 10학생 101·102학생 103
CASCADE삭제두 행 삭제class_id=20 유지
SET NULL삭제두 행 유지, class_id만 NULLclass_id=20 유지
RESTRICT삭제 거부class_id=10 유지class_id=20 유지

CASCADE라면 학생은 세 명에서 한 명으로 줄어듭니다. SET NULL이라면 학생은 세 명 그대로이며, 미배정 상태가 두 명 생깁니다. 학생의 id와 name까지 지우는 것은 아닙니다. RESTRICT에서는 참조 중인 학생이 있으므로 학급 삭제가 거부됩니다. 표의 세 줄은 연속 실행 결과가 아니라 동일한 시작 상태를 가진 세 별도 실험입니다.

CASCADE를 골랐는데 이름만 비워 놓았다고 답하거나, SET NULL을 골랐는데 학생 행 자체를 지웠다고 답했다면 '행 삭제'와 '열 값 변경'을 바꿔 기억한 것입니다. 노트에는 삭제 후 학생 id 목록을 먼저 적어 보세요. CASCADE는 [103], SET NULL은 [101,102,103]이라는 차이가 바로 보입니다.

SET NULL은 다른 제약을 면제하지 않는다

이제 class_id에 NOT NULL도 있다고 가정해 보겠습니다. 학생을 남기는 동시에 배정 학급을 NULL로 바꾸려는 동작은 그 제약과 충돌합니다. PostgreSQL에서 해당 부모 삭제는 실패하며, 'ON DELETE가 있으니 NULL이 허용된다'고 계산하지 않습니다. 삭제 동작을 판정한 다음 남는 행이 나머지 제약도 만족하는지 확인해야 합니다.

반대로 학생 101·102를 먼저 학급 20으로 옮겼다면 학급 10을 참조하는 학생이 없습니다. 그 상태의 학급 10 삭제는 앞의 RESTRICT 사례와 조건이 다릅니다. 단어를 보고 무조건 삭제 거부라고 답하지 말고 현재 참조 행이 있는지부터 살펴보세요. 존재하지 않는 학급 번호로 옮기는 것은 참조 무결성을 만족시키는 해결책이 아닙니다.

NO ACTION을 '아무 검사도 안 함'으로 읽지 않기

PostgreSQL의 NO ACTION도 위반을 허용하는 옵션이 아닙니다. 지연 검사로 설정한 외래 키라면 검사 시점을 늦출 수 있는 반면 RESTRICT는 그런 지연을 허용하지 않는다는 차이가 있습니다. 이 글의 표는 지연 검사가 없는 단순 사례입니다. MySQL 8.4 InnoDB 문서는 NO ACTION을 RESTRICT와 같은 즉시 검사로 설명하므로 DBMS와 엔진 이름을 함께 적어야 합니다.

  1. 삭제문이 어느 테이블의 어떤 키를 대상으로 하는지 동그라미를 칩니다.
  2. 외래 키 화살표를 따라 그 키를 참조하는 자식 행만 고릅니다.
  3. CASCADE이면 그 행을 지우고, SET NULL이면 외래 키 칸만 비우고, RESTRICT이면 참조가 남는지 확인합니다.
  4. 변경 후 NOT NULL 등 다른 제약을 다시 확인합니다.
  5. 영향받지 않은 부모·자식 행도 마지막 표에 남겨 부분 삭제와 전체 삭제를 구분합니다.

외래 키 답안은 동작 이름, 대상 행, 결과 상태를 한 문장에 넣으면 명확해집니다. 예를 들어 '학급 10 삭제에 따라 그 학급을 참조하는 학생 101·102의 class_id만 NULL이 된다'라고 적습니다. '연쇄 처리된다'처럼 대상과 결과가 없는 표현보다 자신의 이해를 확인하기 쉽습니다.

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

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

위 SET NULL 사례에서 학생 101과 102는 학급 10, 학생 103은 학급 20을 참조합니다. 학급 10 삭제 후 학생 수와 학생 103의 class_id는?

정답과 해설 확인학생은 3명이며 학생 103의 class_id는 20입니다.

SET NULL은 삭제된 학급 10을 참조하던 두 학생의 class_id만 NULL로 바꿉니다. 모든 학생의 배정을 지우거나 학생 행을 삭제하지 않습니다. 추가 제약이 없는 예제 조건입니다.