테이블 상속 — INHERITS와 파티셔닝의 뿌리

PostgreSQL INHERITS로 테이블 상속 계층을 구성하는 방법, 부모 조회 시 자식 포함 동작, ONLY 키워드, tableoid를 이용한 자식 테이블 구분, 상속과 선언적 파티셔닝의 관계를 설명합니다.

· 7 min read · PALDYN Team

지난 글에서 자동 증가 키 생성 전략을 살펴봤다. 이번에는 PostgreSQL 고유 기능인 테이블 상속(Table Inheritance)을 다룬다. 객체지향의 상속 개념을 관계형 테이블에 적용한 이 기능은 선언적 파티셔닝의 뿌리이기도 하다.

테이블 상속이란

INHERITS 키워드를 사용하면 자식 테이블이 부모 테이블의 모든 컬럼과 CHECK 제약을 이어받는다. 부모 테이블을 조회하면 자식 테이블의 데이터도 포함된다.

-- 부모 테이블
CREATE TABLE vehicle (
    id    bigint PRIMARY KEY,
    make  text NOT NULL,
    model text,
    year  integer CHECK (year >= 1886),
    price numeric(12,2)
);

-- 자식 테이블: 부모 컬럼 + 고유 컬럼
CREATE TABLE car (
    num_doors integer DEFAULT 4,
    fuel_type text
) INHERITS (vehicle);

CREATE TABLE truck (
    payload_tons numeric(6,2),
    axles        integer
) INHERITS (vehicle);

테이블 상속 계층 구조

데이터 삽입과 조회

-- 자식 테이블에 삽입 (부모 컬럼 포함)
INSERT INTO car (id, make, model, year, price, num_doors, fuel_type)
VALUES (1, 'Hyundai', 'Ioniq6', 2024, 55000000, 4, 'electric');

INSERT INTO truck (id, make, model, year, price, payload_tons)
VALUES (2, 'Hyundai', 'Porter', 2023, 28000000, 1.5);

-- 부모 조회 → 자식 포함
SELECT id, make, year FROM vehicle ORDER BY id;
-- 1 | Hyundai | 2024  (car에서)
-- 2 | Hyundai | 2023  (truck에서)

-- 자식 테이블 구분
SELECT tableoid::regclass AS source, id, make
FROM vehicle;
-- car   | 1 | Hyundai
-- truck | 2 | Hyundai

tableoid는 각 행이 실제로 어느 테이블에 저장되어 있는지 OID를 반환한다. ::regclass로 캐스팅하면 테이블 이름으로 변환된다.

테이블 상속 DDL 패턴

ONLY 키워드

-- 부모 테이블 직접 삽입 데이터만 조회
SELECT * FROM ONLY vehicle;

-- UPDATE/DELETE도 ONLY 사용 가능
UPDATE ONLY vehicle SET price = price * 1.05;  -- 자식 제외
DELETE FROM ONLY vehicle WHERE year < 2000;

ONLY를 쓰지 않으면 모든 자식의 해당 행도 함께 영향을 받는다.

CHECK 제약 상속

부모의 CHECK 제약은 자식에게 상속된다. 자식은 추가 CHECK를 더할 수 있다.

-- 부모의 year >= 1886 제약이 car에도 적용
INSERT INTO car (id, make, year) VALUES (3, 'Test', 1800);
-- ERROR: new row violates check constraint "vehicle_year_check"

-- 자식 전용 추가 제약
ALTER TABLE truck ADD CONSTRAINT chk_payload
    CHECK (payload_tons > 0 AND payload_tons <= 50);

주의: 부모에서 NOT NULL 제약은 상속되지만, UNIQUEPRIMARY KEY는 자식에 자동 상속되지 않는다. 각 자식에 독립적으로 선언해야 한다.

부모 컬럼 추가 → 자식 반영

부모에 컬럼을 추가하면 모든 자식에 자동으로 반영된다.

ALTER TABLE vehicle ADD COLUMN color text;

-- car, truck 모두 color 컬럼 획득
\d car
-- id, make, model, year, price, num_doors, fuel_type, color

반대로 자식에만 컬럼을 추가하면 부모와 다른 자식에는 영향이 없다.

상속 제약

  1. 외래 키 참조 불가: 다른 테이블이 부모를 FK로 참조해도 자식 행을 가리킬 수 없다.
  2. UNIQUE 제약 미상속: 각 자식에서 독립 선언 필요.
  3. 인덱스 미상속: 부모 인덱스는 부모 행에만 적용. 자식 인덱스는 별도 생성.
-- 각 자식에 인덱스 개별 생성
CREATE INDEX idx_car_year ON car (year);
CREATE INDEX idx_truck_year ON truck (year);
-- 부모의 vehicle(year) 인덱스는 자식 조회 시 활용 안 됨

상속 vs 선언적 파티셔닝

PostgreSQL 10에서 선언적 파티셔닝(Declarative Partitioning)이 도입됐다. 내부적으로 상속 메커니즘을 사용하지만, 명시적 PARTITION BY 구문으로 파티션을 관리한다.

기능테이블 상속선언적 파티셔닝
구문INHERITSPARTITION BY
자식 추가INHERITS (부모)PARTITION OF 부모
FK 참조불가pg 12+ 가능
파티션 pruning수동자동
권장모델링 상속대용량 파티셔닝

성능 최적화 목적의 파티셔닝에는 선언적 파티셔닝을 사용하고, 단일 테이블 상속(STI) 패턴처럼 다형성 모델링에는 INHERITS를 활용한다.

단일 테이블 상속 패턴

ORM의 STI 패턴을 PostgreSQL 상속으로 구현할 수 있다.

CREATE TABLE notification (
    id         bigint PRIMARY KEY,
    user_id    bigint NOT NULL,
    created_at timestamptz DEFAULT now(),
    is_read    boolean DEFAULT false
);

CREATE TABLE email_notification (
    subject text,
    body    text
) INHERITS (notification);

CREATE TABLE push_notification (
    device_token text,
    badge_count  integer
) INHERITS (notification);

-- 모든 알림 조회 (미읽음)
SELECT tableoid::regclass AS type, id, user_id
FROM notification
WHERE NOT is_read
ORDER BY created_at DESC;

상속 계층 조회

-- 상속 관계 조회
SELECT parent.relname AS parent, child.relname AS child
FROM pg_inherits i
JOIN pg_class parent ON i.inhparent = parent.oid
JOIN pg_class child  ON i.inhrelid  = child.oid
WHERE parent.relname = 'vehicle';
-- vehicle | car
-- vehicle | truck
-- vehicle | motorcycle

-- 자식 테이블 나열
SELECT c.relname
FROM pg_class c
JOIN pg_inherits i ON c.oid = i.inhrelid
WHERE i.inhparent = 'vehicle'::regclass;

정리

PostgreSQL 테이블 상속은 관계형 모델에 다형성을 도입하는 독특한 기능이다. 부모 테이블 조회 시 자식 데이터를 자동 포함하는 동작, ONLY로 범위 제한, tableoid로 자식 구분하는 패턴을 이해하면 다형성 엔티티 모델링에 효과적으로 활용할 수 있다. 대용량 테이블 분할이 목적이라면 선언적 파티셔닝이 더 적합하다.


지난 글: IDENTITY vs SEQUENCE — 자동 증가 키 생성 전략

다음 글: MVCC — xmin, xmax, ctid로 이해하는 다중 버전 동시성


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