식별 관계 vs 비식별 관계

자식 테이블의 PK에 부모 FK가 포함되는 식별 관계와 독립 PK를 갖는 비식별 관계의 차이, 자연키 vs 서로게이트키 선택 기준, 실무 권장 패턴을 설명합니다.

· 5 min read · PALDYN Team

지난 글에서 ERD를 SQL 테이블로 변환하는 규칙을 살펴봤다. 이번에는 ERD 도구에서 반드시 마주치는 개념인 **식별 관계(Identifying Relationship)**와 **비식별 관계(Non-Identifying Relationship)**의 차이를 정리한다.


핵심 차이: 자식의 PK에 부모 FK가 들어가는가

두 관계의 차이는 단 하나다. 자식 테이블이 부모 테이블의 FK를 자신의 PK(기본키) 안에 포함하느냐 여부다.

식별 vs 비식별 관계


식별 관계 (Identifying Relationship)

자식의 PK가 부모의 PK(FK)를 포함한다. 자식은 부모 없이는 식별될 수 없다. ERD에서는 실선으로 표기한다.

-- 주문(부모) → 주문_항목(자식): 식별 관계
CREATE TABLE orders (
    id   BIGINT PRIMARY KEY
);

CREATE TABLE order_items (
    order_id   BIGINT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
    seq        INT    NOT NULL,  -- 주문 내 순번
    product_id BIGINT NOT NULL,
    qty        INT    NOT NULL DEFAULT 1,
    PRIMARY KEY (order_id, seq)  -- 부모 FK + seq = 복합 PK
);

order_items의 PK는 (order_id, seq)다. order_id가 없으면 order_items 행을 유일하게 식별할 수 없다. 이것이 식별 관계다.


비식별 관계 (Non-Identifying Relationship)

자식의 PK가 부모 FK와 무관하다. 자식은 독립적인 PK를 갖는다. ERD에서는 점선으로 표기한다.

-- 고객(부모) → 주문(자식): 비식별 관계
CREATE TABLE customers (
    id   BIGINT PRIMARY KEY
);

CREATE TABLE orders (
    id          BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    customer_id BIGINT REFERENCES customers(id) ON DELETE SET NULL,
    -- customer_id는 FK지만 PK가 아님
    status      VARCHAR(20) NOT NULL DEFAULT 'pending',
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

orders.id는 완전히 독립적이다. customer_id가 NULL이어도(고객 탈퇴 후) 주문 레코드는 존재할 수 있다.


언제 식별 관계를 쓰나

식별 관계가 자연스러운 경우는 다음과 같다.

-- 1. 약한 엔티티: 부모 없이 의미 없는 자식
CREATE TABLE account_transactions (
    account_id  INT  NOT NULL REFERENCES accounts(id),
    seq         INT  NOT NULL,
    amount      NUMERIC(12,2) NOT NULL,
    PRIMARY KEY (account_id, seq)
);

-- 2. 복합 PK 테이블 (교차 테이블은 이미 식별 관계)
CREATE TABLE role_permissions (
    role_id       INT NOT NULL REFERENCES roles(id),
    permission_id INT NOT NULL REFERENCES permissions(id),
    PRIMARY KEY (role_id, permission_id)
);

-- 3. 이력/로그 테이블: 원본 없이 의미 없는 로그
CREATE TABLE product_price_history (
    product_id INT  NOT NULL REFERENCES products(id),
    seq        INT  NOT NULL,
    price      NUMERIC(10,2) NOT NULL,
    changed_at TIMESTAMPTZ   NOT NULL,
    PRIMARY KEY (product_id, seq)
);

자연키 vs 서로게이트키

식별/비식별 관계를 논의할 때 항상 따라오는 주제가 PK 전략이다.

자연키 vs 서로게이트키

-- 자연키 예: 이메일을 PK로 (식별 관계에서 자식에 전파)
CREATE TABLE users (
    email VARCHAR(255) PRIMARY KEY
);
CREATE TABLE user_settings (
    user_email VARCHAR(255) PRIMARY KEY REFERENCES users(email),
    theme      VARCHAR(20)
);
-- 이메일이 바뀌면? → CASCADE UPDATE 필요, FK 인덱스 크기 ↑

-- 서로게이트키 예: 의미없는 ID
CREATE TABLE users (
    id    BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    email VARCHAR(255) NOT NULL UNIQUE  -- 자연키는 UNIQUE로 보존
);
CREATE TABLE user_settings (
    user_id BIGINT PRIMARY KEY REFERENCES users(id),
    theme   VARCHAR(20)
);
-- user.email이 바뀌어도 FK는 불변

서로게이트키가 실무에서 선호되는 이유는 불변성이다. 비즈니스 값(이메일, 주민번호 등)은 바뀔 수 있지만, 자동 증가 ID는 바뀌지 않는다.


식별 관계의 함정: 복합 PK 전파

식별 관계를 여러 단계 중첩하면 복합 PK가 점점 커진다.

ORDER(id)
  └─ ORDER_ITEM(order_id, seq)  ← 2컬럼 PK
       └─ ORDER_ITEM_TAG(order_id, seq, tag_id)  ← 3컬럼 PK
            └─ ORDER_ITEM_TAG_AUDIT(order_id, seq, tag_id, ts)  ← 4컬럼 PK

FK 인덱스가 비대해지고, ORM에서 복합 PK 처리가 번거로워진다. 깊은 계층에서는 서로게이트키 + 비식별 관계가 더 관리하기 쉽다.


실무 결정 기준

Q: 자식이 부모 없이 논리적으로 존재할 수 있는가?
  → YES: 비식별 관계 (서로게이트 PK)
  → NO:  식별 관계 고려 (약한 엔티티, 교차 테이블)

Q: PK 컬럼이 3개 이상이 되는가?
  → YES: 서로게이트 PK로 교체 검토

Q: 자연키가 변경될 수 있는가?
  → YES: 서로게이트 PK + UNIQUE 제약으로 분리

지난 글: 관계 매핑: ERD를 테이블로

다음 글: 수퍼타입/서브타입 테이블 설계


읽어주셔서 감사합니다. 😊