2025-08-15 22:40

Tags:

SQL 참조 무결성 완벽 가이드 데이터베이스의 숨은 질서, 외래 키(Foreign Key) 정복하기

데이터베이스를 단순한 데이터 창고라고 생각한다면 큰 오산입니다. 잘 설계된 데이터베이스는 데이터 간의 관계를 정의하고, 그 관계가 깨지지 않도록 질서를 유지하는 정교한 시스템입니다. 그 질서의 핵심에 바로 **‘참조(Reference)‘**와 **‘참조 무결성(Referential Integrity)‘**이라는 개념이 있습니다.

이 핸드북은 SQL의 ‘참조’를 구현하는 핵심 도구인 **외래 키(Foreign Key)**에 대한 모든 것을 다룹니다. 외래 키가 왜 필요한지 그 근본적인 이유부터 시작해, 테이블을 설계하고 데이터를 관리할 때 마주하게 될 다양한 시나리오에 대처하는 방법까지, 실용적인 예제와 함께 깊이 있게 설명합니다.

1. ‘참조’는 왜 필요한가? (데이터의 대혼란을 막기 위해)

참조의 필요성을 이해하기 위해, 참조가 없는 세상을 상상해 봅시다. 온라인 서점을 운영하며 Customers(고객) 테이블과 Orders(주문) 테이블을 관리한다고 가정해 보겠습니다.

  • Customers 테이블: customer_id (고객 ID), name (이름)

  • Orders 테이블: order_id (주문 ID), customer_id (주문한 고객 ID), book_name (책 이름)

어느 날, 다음과 같은 끔찍한 상황들이 발생합니다.

  1. 유령 주문 발생: 직원의 실수로 Customers 테이블에 존재하지도 않는 고객 ID(customer_id = 999)를 가진 주문이 Orders 테이블에 추가되었습니다. 이 주문은 누구의 것인지 알 수 없는 ‘유령 데이터’가 됩니다.

  2. 고객 탈퇴 대란: 고객 ‘김철수’(customer_id = 101)가 탈퇴를 요청하여 Customers 테이블에서 그의 정보를 삭제했습니다. 그런데 Orders 테이블에는 여전히 그가 주문했던 기록들이 남아있습니다. 이제 이 주문 기록들은 주인을 잃은 ‘고아 데이터(Orphaned Record)‘가 되어 데이터의 일관성을 해칩니다.

이러한 데이터의 불일치와 무결성 파괴는 시스템 전체의 신뢰도를 떨어뜨리고, 심각한 버그의 원인이 됩니다. 바로 이 문제를 해결하기 위해 관계형 데이터베이스는 **‘참조 무결성 제약조건(Referential Integrity Constraint)‘**을 도입했고, 이를 구현하는 구체적인 기술이 바로 **외래 키(Foreign Key)**입니다.

비유: 외래 키는 ‘부모-자식’ 관계와 같습니다. 자식(Orders 테이블)은 반드시 실존하는 부모(Customers 테이블)가 있어야만 태어날 수 있습니다. 또한, 부모가 사라지면 자식의 거취도 함께 결정되어야 합니다. 데이터베이스는 이 규칙을 강제함으로써 집안(데이터베이스)의 족보가 꼬이는 것을 막아줍니다.

2. 참조의 구조: 기본 키와 외래 키의 만남

참조 관계는 두 테이블을 연결하는 **기본 키(Primary Key)**와 **외래 키(Foreign Key)**를 통해 만들어집니다.

  • 기본 키 (Primary Key, PK): 테이블의 각 행(row)을 유일하게 식별할 수 있는 값입니다. (예: Customers 테이블의 customer_id) 중복될 수 없으며, NULL 값을 가질 수 없습니다. 부모의 역할을 합니다.

  • 외래 키 (Foreign Key, FK): 한 테이블의 열(column)이 다른 테이블의 기본 키를 참조하는 값입니다. (예: Orders 테이블의 customer_id) 자식의 역할을 합니다.

Orders 테이블의 customer_idCustomers 테이블 customer_id의 외래 키로 지정하면, 데이터베이스는 다음과 같은 규칙을 강제로 적용합니다.

Orders 테이블의 customer_id 열에는 반드시 Customers 테이블의 customer_id 열에 존재하는 값만 들어갈 수 있다.

이 간단한 규칙 하나가 앞서 언급된 ‘유령 주문’과 ‘고아 데이터’ 문제를 원천적으로 차단합니다.

3. 외래 키 사용법: 관계 맺어주기

외래 키는 테이블을 생성할 때(CREATE TABLE) 또는 생성된 테이블을 수정할 때(ALTER TABLE) 설정할 수 있습니다.

3.1. 테이블 생성 시 외래 키 지정

Customers 테이블을 먼저 생성하고, 그 다음 Orders 테이블을 생성하면서 외래 키 관계를 정의합니다.

-- 부모 테이블
CREATE TABLE Customers (
    customer_id INT PRIMARY KEY,
    name VARCHAR(50) NOT NULL
);
 
-- 자식 테이블
CREATE TABLE Orders (
    order_id INT PRIMARY KEY,
    book_name VARCHAR(100),
    customer_id INT,
    CONSTRAINT fk_customer -- 제약조건의 이름을 지정 (관리를 위해 권장)
    FOREIGN KEY (customer_id) -- Orders 테이블의 customer_id를 외래 키로 지정
    REFERENCES Customers(customer_id) -- Customers 테이블의 customer_id를 참조
);
  • CONSTRAINT: 제약조건에 고유한 이름을 부여합니다. 나중에 이 제약조건을 수정하거나 삭제할 때 편리합니다.

  • FOREIGN KEY (자식 테이블의 열): 외래 키로 사용할 열을 지정합니다.

  • REFERENCES 부모_테이블(부모_테이블의_기본_키_열): 참조할 부모 테이블과 그 테이블의 기본 키를 지정합니다.

3.2. 기존 테이블에 외래 키 추가

이미 만들어진 테이블에는 ALTER TABLE을 사용하여 외래 키 제약조건을 추가할 수 있습니다.

ALTER TABLE Orders
ADD CONSTRAINT fk_customer
FOREIGN KEY (customer_id)
REFERENCES Customers(customer_id);

4. 참조 관계의 관리: ON UPDATE와 ON DELETE 옵션

참조 무결성의 진정한 힘은 부모 테이블의 데이터가 변경(UPDATE)되거나 삭제(DELETE)될 때 어떻게 대응할지를 정의하는 옵션에서 나옵니다.

만약 고객 ‘김철수’(customer_id = 101)를 삭제하려고 할 때, 데이터베이스는 그의 주문 기록이 Orders 테이블에 남아있기 때문에 기본적으로 이 요청을 거부하며 오류를 발생시킵니다. 하지만 우리는 이 상황을 더 능동적으로 제어할 수 있습니다.

ON UPDATEON DELETE 옵션을 통해 다음과 같은 정책을 설정할 수 있습니다.

  • RESTRICT (또는 NO ACTION): 기본값입니다. 자식 테이블에 참조하는 데이터가 남아있는 한, 부모 데이터의 수정/삭제를 허용하지 않습니다. (가장 안전하고 일반적인 정책)

  • CASCADE: 부모 데이터가 수정/삭제되면, 자식 테이블에서 참조하던 데이터도 함께 수정/삭제됩니다.

    • ON DELETE CASCADE: Customers에서 고객을 삭제하면, Orders에 있던 해당 고객의 모든 주문 기록이 자동으로 삭제됩니다. 회원 탈퇴 시 관련 데이터를 모두 정리해야 할 때 유용합니다.

    • ON UPDATE CASCADE: Customers에서 고객 ID를 변경하면(101 201), Orders에 있던 모든 101 ID가 201로 자동 업데이트됩니다. (기본 키는 잘 변경하지 않으므로 자주 사용되지는 않습니다.)

  • SET NULL: 부모 데이터가 수정/삭제되면, 자식 테이블의 해당 외래 키 값을 **NULL**로 설정합니다.

    • ON DELETE SET NULL: 고객을 삭제해도 주문 기록은 남겨두되, 누가 주문했는지는 알 수 없도록 customer_idNULL로 바꿉니다. 이 옵션을 사용하려면 외래 키 열이 NULL을 허용하도록 설정되어 있어야 합니다.
  • SET DEFAULT: 부모 데이터가 수정/삭제되면, 자식 테이블의 해당 외래 키 값을 **미리 지정된 기본값(DEFAULT)**으로 설정합니다.

옵션 적용 예시:

CREATE TABLE Orders (
    order_id INT PRIMARY KEY,
    book_name VARCHAR(100),
    customer_id INT,
    CONSTRAINT fk_customer
    FOREIGN KEY (customer_id)
    REFERENCES Customers(customer_id)
    ON DELETE CASCADE -- 부모 삭제 시 자식도 함께 삭제
    ON UPDATE RESTRICT; -- 부모 수정은 기본적으로 금지
);
옵션장점주의점
CASCADE데이터의 완전한 동기화를 보장.의도치 않은 대량의 데이터 삭제가 발생할 수 있어 신중하게 사용해야 함.
SET NULL자식 데이터는 보존하면서 연결만 끊음.데이터의 의미가 모호해질 수 있음 (주인 없는 주문). 외래 키 열이 NULL 허용이어야 함.
RESTRICT데이터 손실을 막는 가장 안전한 방법.데이터를 삭제하려면 반드시 자식 데이터를 먼저 처리해야 하는 번거로움이 있음.

5. 언제 무엇을 써야 할까?

  • 강력한 종속 관계 (게시글과 댓글): 게시글이 삭제되면 댓글도 의미가 없어지는 경우. **ON DELETE CASCADE**가 적합합니다.

  • 느슨한 관계 (직원과 부서): 특정 부서가 해체되어도 직원은 다른 부서로 발령받거나 ‘미지정’ 상태로 남을 수 있는 경우. ON DELETE SET NULL 또는 **ON DELETE RESTRICT**를 사용하고 애플리케이션 레벨에서 후속 조치를 취하는 것이 좋습니다.

  • 대부분의 경우: 데이터의 안정성을 최우선으로 생각한다면 기본값인 **RESTRICT**를 사용하고, 데이터 변경/삭제 로직을 애플리케이션에서 명시적으로 처리하는 것이 가장 안전합니다.

6. 결론: 데이터베이스의 신뢰를 구축하는 초석

SQL의 참조, 즉 외래 키는 단순히 두 테이블을 연결하는 선 이상의 의미를 가집니다. 이는 데이터베이스 스스로가 데이터의 일관성과 무결성을 지키도록 하는 강력한 규칙이자 약속입니다.

외래 키를 올바르게 사용하면 ‘유령 데이터’나 ‘고아 데이터’가 발생하는 것을 막고, 데이터 간의 논리적 관계를 명확하게 하여 시스템 전체의 안정성과 신뢰도를 크게 향상시킬 수 있습니다. 처음에는 제약조건 때문에 데이터 입력이나 삭제가 번거롭게 느껴질 수 있지만, 장기적으로는 예측 불가능한 데이터 오류로부터 여러분의 시스템을 지켜주는 든든한 수호자가 될 것입니다.

레퍼런스(References)

참조 데이터 무결성