데이터베이스 용어 사전
설계·정규화anomaly · 삽입 이상 · 갱신 이상 · 삭제 이상

이상 현상

중복 때문에 삽입·갱신·삭제가 뜻대로 되지 않는 세 가지 문제. 정규화가 없애려는 대상이다.

한 테이블에 여러 주제를 욱여넣어 같은 사실이 중복될 때 생기는 세 가지 문제. 정규화의 존재 이유다.

문제가 되는 테이블

주문ID회원ID회원등급상품명가격
142GOLD키보드50000
242GOLD마우스30000
399SILVER키보드50000

회원 42의 등급(GOLD)과 키보드 가격(50000)이 여러 행에 중복돼 있다. 여기서 셋이 나온다.

세 가지

  • 삽입 이상 — 주문하지 않은 신규 회원을 넣을 수 없다

    • 상품명·가격 칸을 NULL 로 채운 가짜 행을 만들어야 한다
  • 갱신 이상 — 회원 42가 PLATINUM 이 되면 행 1·2를 전부 고쳐야 한다

    • 하나라도 빠뜨리면 같은 회원의 등급이 두 개가 된다 (모순)
  • 삭제 이상 — 행 3을 지우면 '키보드 가격이 50000' 이라는 사실까지 사라진다

    • 지우고 싶지 않은 정보가 딸려 나간다

실제로 어떻게 터지나

-- 갱신 이상이 만드는 데이터 모순
UPDATE orders SET member_grade = 'PLATINUM'
 WHERE member_id = 42 AND order_id = 1;      -- 행 1만 고쳤다

SELECT DISTINCT member_id, member_grade FROM orders WHERE member_id = 42;
-- 42 | PLATINUM
-- 42 | GOLD          ← 한 회원에게 등급이 둘. 어느 게 진짜인가?

이 상태가 되면 어떤 값이 옳은지 DB가 알 수 없다. 등급별 할인을 계산하는 쿼리가 조회 방식에 따라 다른 답을 내기 시작한다.

해결 — 사실을 한 곳에만 둔다

-- 하나의 넓은 표를 주제별로 쪼갠다
CREATE TABLE member  (member_id BIGINT PRIMARY KEY, grade VARCHAR(20));
CREATE TABLE product (name VARCHAR(100) PRIMARY KEY, price INT);
CREATE TABLE orders  (order_id BIGINT PRIMARY KEY,
                      member_id BIGINT REFERENCES member(member_id),
                      product_name VARCHAR(100) REFERENCES product(name));

-- 이제 등급 변경은 한 행만 고치면 된다
UPDATE member SET grade = 'PLATINUM' WHERE member_id = 42;

각 사실이 정확히 한 곳에만 존재하므로 모순이 구조적으로 불가능해진다.

면접 함정

  • "이상 현상은 데이터가 잘못 들어가서 생긴다" → 데이터가 아니라 스키마 설계가 원인이다. 아무리 조심해서 넣어도 구조가 그대로면 반복된다.
  • "제약(FK·UNIQUE)을 걸면 해결된다" → 중복 자체를 못 막는다. 테이블을 쪼개야 한다.

이미 터진 모순을 찾아내기

-- 같은 회원인데 등급이 여러 개 → 갱신 이상이 이미 발생한 상태
SELECT member_id, count(DISTINCT member_grade) AS variants,
       string_agg(DISTINCT member_grade, ', ') AS values
  FROM orders GROUP BY member_id HAVING count(DISTINCT member_grade) > 1;

레거시 테이블을 정규화하기 전 실태 조사에 그대로 쓴다. 결과가 많으면 어느 값이 진짜인지 정하는 작업부터 해야 한다.

옮기는 절차

-- ① 새 테이블을 만들고 대표값을 뽑아 채운다 (가장 최근 주문의 등급을 채택)
INSERT INTO member (id, grade)
SELECT DISTINCT ON (member_id) member_id, member_grade
  FROM orders ORDER BY member_id, created_at DESC;

-- ② 양쪽을 함께 쓰며 검증한다 (일정 기간)
-- ③ 읽기를 새 테이블로 전환한다
-- ④ 옛 칼럼을 제거한다
ALTER TABLE orders DROP COLUMN member_grade;

한 번에 바꾸지 않는다 — ②의 병행 기간이 사고를 막는다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 정규화 — 이상현상·함수종속·1NF~BCNF·반정규화