Skip to Content
자격증SQLD07. 정규화 2: BCNF와 반정규화

이번 문서의 목표: 이 문서를 다 읽으면 3NF를 만족해도 BCNF는 위반할 수 있는 경우를 판별하고, 정규화 수준을 올릴수록 왜 조회 성능이 떨어지는지 설명하며, 실무·시험에서 등장하는 반정규화 기법을 테이블·컬럼·관계 단위로 구분해 적용할 수 있다.

BCNF: 3NF보다 한 단계 더 엄격한 정규형

왜 필요한가

06편에서 제3정규형(3NF)까지 배웠다. 3NF는 “이행적 함수 종속(A→B, B→C이면 A→C인 종속 관계)을 제거한 상태”라고 정의했다. 그런데 흥미롭게도, 어떤 테이블은 3NF의 조건을 완벽히 만족하면서도 여전히 이상현상(anomaly, 데이터를 넣고·고치고·지울 때 발생하는 부작용)이 남아 있을 수 있다. 이 틈을 메우려고 제안된 정규형이 BCNF(Boyce-Codd Normal Form, 보이스-코드 정규형)다.

쉽게 말하면: 3NF는 “일반 속성끼리의 종속”만 감시하는데, BCNF는 한 걸음 더 나가 “후보키 자격이 없는 속성이 다른 속성을 결정하는 상황”까지 감시한다.

정의: 결정자와 후보키의 관계

BCNF를 이해하려면 먼저 함수적 종속(functional dependency, FD)과 결정자(determinant)라는 용어를 정확히 알아야 한다. A→B라는 함수적 종속은 “A의 값이 정해지면 B의 값도 자동으로 하나로 정해진다”는 뜻이고, 이때 왼쪽에 있는 A를 결정자라 부른다. 04편에서 다룬 후보키(candidate key, 유일성과 최소성을 모두 만족하는 속성 조합)를 떠올려 보면, 후보키는 사실 그 테이블 전체를 결정하는 가장 강력한 결정자다.

BCNF의 조건은 단 한 문장으로 정리된다.

테이블에 존재하는 모든 결정자가 후보키여야 한다.

거꾸로 말하면, “후보키 자격이 없는데도 다른 속성을 결정해 버리는 속성”이 하나라도 있으면 그 테이블은 BCNF를 위반한다.

작은 예시: 수강 테이블

다음과 같은 수강 테이블이 있다고 하자.

학생번호과목명교수명
1000데이터베이스김민준
1001데이터베이스김민준
1002자료구조이서연
1003웹프로그래밍박도윤

여기에는 두 가지 규칙(제약 조건)이 있다고 가정한다. 첫째, 한 학생은 같은 과목을 중복 수강하지 않는다. 둘째, 한 교수는 오직 한 과목만 담당한다(이 예시만의 단순화된 가정이다. 실제 학사 시스템은 한 교수가 여러 과목을 담당할 수 있지만, BCNF 위반 상황을 보여 주려고 일부러 단순화했다).

이 두 규칙을 함수적 종속으로 옮기면 다음과 같다.

  • (학생번호, 과목명) → 교수명 — 학생과 과목이 정해지면 담당 교수도 하나로 정해진다.
  • 교수명 → 과목명 — 교수가 정해지면 그 교수가 담당하는 과목도 하나로 정해진다(한 교수가 한 과목만 담당하므로).

이 테이블의 후보키는 무엇일까? (학생번호, 과목명)은 물론 후보키다(두 값을 알면 교수명까지 전부 결정된다). 그런데 (학생번호, 교수명)도 후보키가 된다. 학생번호와 교수명을 알면, 교수명→과목명 종속에 의해 과목명까지 자동으로 결정되기 때문이다. 즉 이 테이블의 후보키는 (학생번호, 과목명)과 (학생번호, 교수명) 두 개다.

계산·적용: 어디서 BCNF가 깨지는가

문제는 두 번째 종속인 교수명 → 과목명이다. 교수명은 분명히 다른 속성(과목명)을 결정하는 결정자다. 하지만 교수명 하나만으로는 이 테이블의 행을 유일하게 구분할 수 없다(같은 교수 이름이라도 학생번호가 다른 여러 행이 존재한다). 즉 교수명은 결정자이지만 후보키는 아니다. 이것이 정확히 BCNF가 금지하는 상황이다.

그렇다면 왜 3NF는 이 문제를 잡아내지 못할까? 3NF는 “기본키가 아닌 일반 속성이 다른 일반 속성을 결정하는 이행적 종속”만 감시하는데, 과목명은 후보키 (학생번호, 과목명)의 구성 요소이므로 프라임 속성(prime attribute, 어떤 후보키에라도 속해 있는 속성)이다. 3NF의 정의는 “일반 속성 간의 이행적 종속”을 문제 삼기 때문에, 프라임 속성인 과목명이 얽힌 이 종속은 3NF 검사망을 그대로 통과해 버린다. BCNF는 프라임 속성 여부를 따지지 않고 “결정자면 무조건 후보키여야 한다”는 더 엄격한 기준을 적용하므로 이 허점을 잡아낸다.

이 테이블을 BCNF로 분해하면 다음과 같이 두 테이블로 나뉜다.

수강(학생번호, 교수명)과 담당교수(교수명, 과목명)로 나누면, 담당교수 테이블에서는 교수명이 곧 기본키이므로 “결정자 = 후보키” 조건이 성립하고, 수강 테이블에도 더 이상 후보키가 아닌 결정자가 남아 있지 않다.

결과 해석

분해 전 테이블에서는 “박도윤 교수가 담당 과목을 웹프로그래밍에서 모바일프로그래밍으로 바꾼다”는 사실 하나를 반영하려면, 박도윤 교수를 수강하는 모든 학생의 행을 일일이 찾아 과목명 컬럼을 전부 고쳐야 한다. 한 행이라도 빠뜨리면 “박도윤 교수가 두 과목을 동시에 담당하는” 모순된 데이터가 남는다. 이것이 갱신 이상(update anomaly)이다. 분해 후에는 담당교수 테이블의 딱 한 행만 고치면 되므로 이 문제가 사라진다.

자주 틀리는 점

기출에서는 “BCNF를 적용하면 함수적 종속 자체가 사라지거나, 테이블 간 관계가 완전히 없어진다”는 식의 오답 보기가 반복해서 나온다. 이는 사실이 아니다. BCNF 분해는 종속 관계를 없애는 것이 아니라, 종속 관계가 성립하는 단위로 테이블을 나누는 것이다. 분해된 두 테이블 사이의 관계는 08편에서 다룰 외래키(foreign key, FK)를 통해 그대로 유지된다. 또한 “1NF·2NF·BCNF는 키(key)를 기준으로 정의되지만, 3NF만 유일하게 일반 속성 간의 종속을 다룬다”는 구분점도 자주 출제되므로, BCNF를 “3NF의 강화판”이라고만 뭉뚱그리지 말고 “결정자-후보키 관계를 따진다”는 고유한 기준으로 기억해야 한다.

정규화 수준과 조회 성능의 상충 관계

왜 필요한가

정규화(normalization)는 데이터 중복을 없애고 이상현상을 막아 준다는 점에서 명백히 이롭다. 그런데 왜 실무에서는 “정규화를 너무 심하게 하면 안 된다”는 말이 나올까? 그 답은 정규화의 본질적인 부작용에 있다.

쉽게 말하면: 정규화는 큰 테이블 하나를 여러 개의 작은 테이블로 쪼개는 작업이다. 쪼개진 정보를 다시 합쳐서 보려면 16편에서 배울 조인(JOIN)을 거쳐야 하는데, 테이블이 많이 쪼개질수록 조인 횟수도 늘어나 조회 속도가 느려질 수 있다.

정의: 쓰기 성능·무결성 vs 읽기 성능

정규화된 테이블 구조는 데이터를 입력·수정·삭제할 때 유리하다. 값 하나를 고칠 곳이 단 한 군데뿐이기 때문이다(BCNF 예시에서 담당 교수의 과목을 바꿀 때 한 행만 고치면 됐던 것처럼). 반면 데이터를 조회할 때는 흩어진 여러 테이블을 다시 모아야 하므로 조인이 필요하고, 조인은 두 테이블의 행을 비교해 짝을 맞추는 연산이라 테이블 크기가 크거나 조인 단계가 많아질수록 처리 시간이 늘어난다.

작은 예시

주문 정보를 정규화하면 보통 주문(주문번호, 고객번호, 주문일자)과 고객(고객번호, 고객명, 등급)처럼 나뉜다. “이번 달 주문 목록에 고객명까지 함께 표시해 달라”는 조회 요청이 들어오면, 두 테이블을 고객번호로 조인해야 고객명을 가져올 수 있다.

SELECT o.주문번호, o.주문일자, c.고객명 FROM 주문 o JOIN 고객 c ON o.고객번호 = c.고객번호 WHERE o.주문일자 >= DATE '2026-09-01';

이 조회가 하루에도 수만 번 실행되는 화면(예: 주문 목록 페이지)이라면, 매번 두 테이블을 조인하는 비용이 누적되어 시스템 전체의 부담으로 커진다. 만약 주문 테이블에 고객명 컬럼을 미리 중복 저장해 두면, 이 조회는 조인 없이 주문 테이블 하나만 읽으면 끝난다. 이렇게 의도적으로 정규화 원칙을 깨고 성능을 우선하는 기법이 바로 반정규화(denormalization)다.

결과 해석

정규화와 반정규화는 “어느 한쪽이 항상 옳다”는 관계가 아니라 트레이드오프(trade-off, 하나를 얻으려면 다른 하나를 포기해야 하는 관계) 관계다. 09편에서 다룰 트랜잭션 처리가 잦고 데이터 정합성이 중요한 시스템(계좌 이체, 재고 관리)은 정규화 수준을 높게 유지하는 쪽이 안전하다. 반대로 통계·리포트처럼 대량의 데이터를 자주 읽기만 하고 수정은 거의 없는 영역은 반정규화로 조회 성능을 확보하는 편이 실용적이다.

자주 틀리는 점

“정규화 수준은 무조건 높을수록 좋다”는 생각이 대표적인 함정이다. 시험은 이 대비되는 두 시나리오(쓰기 위주 vs 읽기 위주 시스템)를 제시하고 어느 쪽에 반정규화가 더 필요한지 묻는 형태로 자주 출제된다. 정규화는 무결성을, 반정규화는 성능을 우선한다는 목적의 차이를 기준으로 판단해야 한다.

반정규화: 대상과 기법

왜 필요한가

반정규화가 필요하다는 판단이 서면, 실제로 “무엇을” 반정규화할지 결정해야 한다. 반정규화는 무작정 아무 데나 중복 컬럼을 추가하는 것이 아니라, 테이블 단위·컬럼 단위·관계 단위로 정해진 기법을 따라 적용한다.

쉽게 말하면: 테이블 반정규화는 “테이블을 합치거나 쪼갠다”, 컬럼 반정규화는 “자주 쓰는 값을 미리 계산해 컬럼으로 저장해 둔다”, 관계 반정규화는 “멀리 돌아가는 조인 경로를 지름길로 만든다”는 뜻이다.

정의: 세 가지 반정규화 대상

대상대표 기법설명예시
테이블 반정규화테이블 병합자주 함께 조회되는 1:1 또는 1:N 관계의 두 테이블을 하나로 합쳐 조인을 없앤다회원(회원번호, 이름)과 회원상세(회원번호, 주소, 연락처)를 회원 테이블 하나로 병합
테이블 반정규화테이블 분할자주 쓰는 컬럼과 드물게 쓰는 컬럼을 수직 분할하거나, 특정 조건의 행만 수평 분할해 조회 범위를 줄인다게시글 테이블에서 본문(대용량 컬럼)만 별도 테이블로 수직 분할
테이블 반정규화슈퍼타입·서브타입 병합05편에서 다룬 슈퍼타입·서브타입 구조를 하나의 테이블로 통합해 조인 없이 전체를 조회한다차량(슈퍼타입)과 승용차·화물차(서브타입)를 차량 테이블 하나로 병합
컬럼 반정규화중복 컬럼 추가조인해야만 얻을 수 있는 값을 대상 테이블에 미리 복사해 둔다주문 테이블에 고객 테이블의 고객명을 미리 복사
컬럼 반정규화파생 컬럼 추가집계·계산 결과를 매번 계산하지 않고 컬럼으로 저장해 둔다주문 테이블에 SUM으로 매번 계산하던 총주문금액 컬럼을 미리 저장
컬럼 반정규화이력 데이터 컬럼 추가변경 이력을 보존하려고 최신 값 외에 이전 값이나 변경 시점 컬럼을 추가한다상품 테이블에 이전가격, 가격변경일 컬럼 추가
관계 반정규화중복 관계(조인 경로 단축) 추가여러 단계를 거쳐야 닿는 테이블에 직접 외래키를 하나 더 연결해 조인 단계를 줄인다주문 → 배송 → 배송기사로 이어지던 경로에, 주문 테이블에 배송기사번호를 직접 추가

작은 예시와 계산·적용

컬럼 반정규화 중 파생 컬럼을 추가하는 상황을 SQL로 살펴보자. 주문상세 테이블에서 매번 합계를 계산하는 대신, 주문 테이블에 총주문금액 컬럼을 미리 저장해 둔 경우다.

-- 반정규화 이전: 조회할 때마다 집계 연산이 필요 SELECT 주문번호, SUM(수량 * 단가) AS 총주문금액 FROM 주문상세 GROUP BY 주문번호; -- 반정규화 이후: 주문 테이블에 총주문금액 컬럼을 미리 저장 SELECT 주문번호, 총주문금액 FROM 주문;

두 번째 방식은 주문상세 테이블을 전혀 건드리지 않고 조회가 끝나므로, 15편에서 배울 GROUP BY·집계 연산 없이 결과를 즉시 얻는다. 대신 대가가 따른다. 주문상세에 새 항목이 추가되거나 수정될 때마다, 주문 테이블의 총주문금액 컬럼도 함께 갱신해 줘야 한다. 이 갱신을 애플리케이션 로직이나 11편에서 다룰 트리거로 관리하지 않으면, 두 값이 서로 어긋나는 데이터 불일치가 발생한다.

결과 해석

반정규화는 “조회 성능”이라는 뚜렷한 이득을 주지만, 그 대가로 데이터 중복동기화 책임을 함께 떠안는다. 총주문금액처럼 파생된 값을 여러 곳에 저장하면, 원본(주문상세)이 바뀔 때마다 사본(주문의 총주문금액)도 함께 고쳐야 하는 관리 부담이 생긴다.

자주 틀리는 점

56회 기출에는 “반정규화는 데이터 중복으로 독립성이 떨어진다”는 보기가 옳은 설명으로 등장한 사례가 있다. 반정규화를 무조건 “성능을 올려 주는 좋은 기법”으로만 외우면 이런 보기에서 판단이 흔들린다. 반정규화는 조회 성능이라는 장점과, 데이터 중복·갱신 이상 위험이라는 단점을 함께 가진 절충안이라는 양면성을 반드시 함께 기억해야 한다. 또한 시험은 “반정규화는 정규화 규칙을 지키면서 성능을 높이는 기법이다”처럼 정의 자체를 왜곡한 보기도 자주 낸다. 반정규화는 이름 그대로 정규화 원칙을 의도적으로 역행하는 기법이지, 정규화 규칙 안에서 이뤄지는 최적화가 아니다.

직접 해보기

00편에서 만든 Oracle 환경에서 진행합니다. seed.sql을 아직 안 넣었다면 00편을 먼저 보세요.

앞서 본 반정규화 예시(총주문금액 컬럼)를 seed 데이터로 직접 확인해 본다. 주문 테이블의 금액 컬럼이 바로 그 반정규화된 값이고, 주문상세를 집계하면 같은 값을 다시 계산해낼 수 있다.

-- 1) 정규화된 원본에서 매번 계산 (조인 + 집계 필요) SELECT o.주문번호, SUM(od.수량 * od.단가) AS 합계금액 FROM 주문 o JOIN 주문상세 od ON o.주문번호 = od.주문번호 GROUP BY o.주문번호 ORDER BY o.주문번호;
-- 2) 반정규화된 컬럼을 그대로 읽기 (조인·집계 없음) SELECT 주문번호, 금액 AS 합계금액 FROM 주문 ORDER BY 주문번호;

무엇을 보아야 하나: 두 쿼리의 합계금액 값이 주문번호별로 같은지, 그리고 두 번째 쿼리에는 JOINGROUP BY도 없다는 점을 비교해 보세요.

왜 이걸 해보나: 반정규화는 “결과는 똑같은데 쿼리가 훨씬 단순해진다”는 감각을 몸으로 익혀야, 시험에서 “왜 굳이 중복 컬럼을 두는가”를 묻는 문제에서 흔들리지 않는다.

핵심 정리

  • BCNF는 “모든 결정자가 후보키여야 한다”는 조건으로, 후보키가 아닌 속성이 다른 속성을 결정하는 경우까지 잡아내는 3NF보다 더 엄격한 정규형이다.
  • 정규화는 테이블을 잘게 쪼개 쓰기·무결성 측면에서 유리하지만, 조회 시 조인이 늘어나 읽기 성능이 떨어질 수 있다. 이 트레이드오프가 반정규화 도입의 근거가 된다.
  • 반정규화는 테이블 단위(병합·분할·슈퍼타입 병합), 컬럼 단위(중복·파생·이력 컬럼 추가), 관계 단위(조인 경로 단축)로 나뉘며, 조회 성능을 얻는 대신 데이터 중복과 갱신 이상 위험을 감수해야 한다.
  • BCNF 분해는 종속 관계나 테이블 간 연관 자체를 없애는 것이 아니라, 그 관계가 외래키로 유지되도록 테이블을 재구성하는 작업이다.

마무리 복습

문제 14지선다
BCNF의 조건으로 가장 정확한 것은?
문제 24지선다
수강 테이블(학생번호, 과목명, 교수명)에서 한 학생은 같은 과목을 중복 수강하지 않고, 한 교수는 오직 한 과목만 담당한다고 할 때, 이 테이블이 3NF는 만족하지만 BCNF는 위반하는 이유로 옳은 것은?
문제 34지선다
정규화 수준을 높일수록 일반적으로 나타나는 현상으로 가장 적절한 것은?
문제 44지선다
다음 중 반정규화의 대상과 기법을 잘못 짝지은 것은?
문제 54지선다
주문상세 테이블의 집계값을 매번 계산하는 대신 주문 테이블에 총주문금액 컬럼을 미리 저장했을 때 새로 생기는 위험으로 가장 적절한 것은?
문제 64지선다
정규화와 반정규화의 관계에 대한 설명으로 가장 적절한 것은?

참고 자료

Last updated on