데이터베이스 용어 사전
제약·뷰CHECK · UNIQUE 제약 · NOT NULL

제약

값이 지켜야 할 규칙을 DB에 못 박는 것. 애플리케이션 버그가 데이터를 망가뜨리는 것을 막는 마지막 방어선이다.

값이 지켜야 할 규칙을 DB 스키마에 못 박는 것. 애플리케이션이 여러 개여도, 배치가 직접 SQL을 쏴도 뚫리지 않는다.

종류

CREATE TABLE product (
  id       BIGINT PRIMARY KEY,                          -- 유일 + NOT NULL
  sku      VARCHAR(50) NOT NULL UNIQUE,                 -- 유일 (NULL 은 여러 개 가능)
  price    INT NOT NULL CHECK (price >= 0),             -- 값 범위
  status   VARCHAR(20) NOT NULL DEFAULT 'DRAFT'
           CHECK (status IN ('DRAFT','ON_SALE','SOLD_OUT')),
  seller_id BIGINT NOT NULL REFERENCES seller(id)       -- 참조 무결성
);

왜 애플리케이션 검증만으로 부족한가

  • 애플리케이션이 하나가 아니다 — 웹 · 배치 · 어드민 · 데이터 이관 스크립트

  • 사람이 직접 SQL 을 쏜다 — 운영 중 수동 보정

  • 버그는 반드시 난다 — 검증을 빠뜨린 경로가 생긴다

  • DB 제약은 모든 경로가 반드시 통과하는 단 하나의 지점이다

애플리케이션 검증은 UX를 위해(친절한 오류 메시지), DB 제약은 데이터를 위해 존재한다. 둘 다 필요하다.

운영 중 제약 추가 — 락을 조심한다

-- ❌ 기존 행을 전부 검사하는 동안 테이블이 잠긴다
ALTER TABLE product ADD CONSTRAINT chk_price CHECK (price >= 0);

-- ✅ 두 단계로 나눈다 (PostgreSQL)
ALTER TABLE product ADD CONSTRAINT chk_price CHECK (price >= 0) NOT VALID;  -- 즉시. 이후 데이터만 검사
ALTER TABLE product VALIDATE CONSTRAINT chk_price;                          -- 약한 락으로 기존 행 검사

대형 테이블에서 이 차이가 서비스 중단 여부를 가른다.

유니크 인덱스를 온라인으로 만들기

-- PostgreSQL: 쓰기를 막지 않고 인덱스를 만든 뒤 제약으로 승격한다
CREATE UNIQUE INDEX CONCURRENTLY uq_sku ON product (sku);
ALTER TABLE product ADD CONSTRAINT uq_sku UNIQUE USING INDEX uq_sku;

조건부 유일성

-- 삭제되지 않은 행에서만 sku 가 유일해야 한다
CREATE UNIQUE INDEX uq_sku_alive ON product (sku) WHERE deleted_at IS NULL;

부분 유니크 인덱스는 소프트 삭제와 유일성을 함께 쓸 때의 표준 해법이다. MySQL에는 없어서 별도 칼럼 트릭을 쓴다.

MySQL의 CHECK는 늦게 왔다

  • MySQL 8.0.16 미만 — CHECK 를 문법상 받아들이지만 무시했다 (조용히!)
  • MySQL 8.0.16 이상 — 실제로 강제한다

옛 버전 스키마를 그대로 옮겨 왔다면 CHECK가 실제로 동작하는지 확인해 봐야 한다.

면접 함정

  • "제약은 성능을 떨어뜨리니 빼는 게 좋다" → 검사 비용보다 데이터가 망가진 뒤의 복구 비용이 훨씬 크다.
  • "애플리케이션에서 검증하니 DB 제약은 중복" → 경로가 하나뿐이라는 가정이 틀렸다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 제약·트리거·뷰 — DB가 지키는 규칙·가상 테이블