9월8일(화)

Win Cha·2026년 9월 10일

정규화(Normalization)와 비정규화(Denormalization, 역정규화)

우리가 앞서 SQL에서 여러 테이블을 조인(JOIN)하고 그룹화(GROUP BY)하는 유용한 방법들을 배웠습니다. 그렇다면 애초에 "테이블은 어떤 기준으로 쪼개어 설계해야 하며, 또 어떤 상황에서는 다시 합쳐야 할까?"라는 근본적인 아키텍처 고민이 생기게 됩니다.

그 해답이 바로 정규화와 비정규화입니다. 아주 직관적인 비유와 구체적인 사례를 통해 구조적으로 정리해 드릴게요!


1. 정규화 (Normalization): 데이터의 최소 단위 분할

'초간단 정규화' 요약장

제1정규화 : 하나의 셀(칸)에는 무조건 단 한 개의 데이터만 넣는다.
한 칸에 쉼표(,) 찍고 억지로 데이터 욱여넣기 방지
제2정규화 : 공동 대장 ID(복합 기본키) 중, 한쪽 대장에게만 아양 떠는 열은 분리한다.
부부 공동 명의 방에 남편 개인 낚시 장비 쑤셔 넣기 방지
*제3정규화 : 대장 ID가 아닌, 일반 데이터끼리 꼬리를 물고 종속되는 관계는 분리한다.
내 수첩에 친구의 사촌 동생 전화번호까지 같이 적어두기 방지

① 정규화의 정의와 목적

  • 정의: 관계형 데이터베이스 설계 시 데이터의 중복을 최소화하고, 데이터의 일관성과 정확성을 지키는 무결성(Integrity)을 유지하기 위해 테이블을 일정한 규칙에 따라 구조적으로 쪼개어 나가는 과정입니다. 주로 논리적 설계 단계에서 수행됩니다.
  • 왜 필요한가요? (이상 현상 방지):
    하나의 큰 테이블에 성격이 다른 데이터를 한꺼번에 저장하면 이상 현상(Anomaly)이 발생합니다.
    • 삽입 이상: 새 데이터를 추가하고 싶은데 불필요한 정보까지 억지로 함께 입력해야 하는 상황.
    • 삭제 이상: 특정 정보를 지우고 싶은데, 그 행에 묶여 있는 다른 중요한 정보까지 원치 않게 같이 지워지는 상황.
    • 수정 이상: 데이터가 여러 곳에 중복 저장되어 있어서 한 군데만 수정하면 데이터의 일관성이 깨지는 상황.

② 아주 쉬운 비유

  • 옷장에 외투, 양말, 신발, 모자를 그냥 한데 섞어서 무작정 쑤셔 박아둔(?) 상태를 상상해 보세요. 양말 하나를 꺼내려다 외투가 쏟아지고 먼지가 쌓입니다.
  • 정규화는 이 혼란스러운 옷장에 양말 정리함, 외투 걸이, 신발장으로 서랍을 분리하여 딱 알맞은 제자리에 깔끔하게 보관하는 정리정돈 과정입니다.

③ 대표적인 정규화 단계 (핵심 3종 세트)

  • 제1정규형 (1NF): 한 칸(원자 값)에는 반드시 딱 하나의 데이터만 넣기 (예: 한 회원의 '전화번호' 칸에 번호 2개를 쉼표로 같이 적어두지 않기).
  • 제2정규형 (2NF): 기본키(PK)의 일부분에만 종속되는 열이 없도록 분리하기 (복합 기본키 상황에서 일부에만 종속되는 컬럼을 별도 테이블로 쪼개기).
  • 제3정규형 (3NF): 기본키가 아닌 일반 열들끼리 서로 종속되지 않도록 분리하기 (예: '학번 ➡️ 학과 ➡️ 학과 사무실 전화번호' 관계에서 학과 사무실 전화번호는 학번에 종속되는 게 아니라 학과에 종속되므로 학과 테이블을 따로 분리).

2. 비정규화 (Denormalization): 성능을 위한 전략적 합치기

① 비정규화의 정의와 목적

  • 정의: 정규화된 데이터베이스에서 시스템의 조회(SELECT) 성능을 극적으로 높이기 위해, 의도적으로 데이터 중복을 허용하거나 테이블을 다시 통합하는 최적화 기법입니다. 주로 물리적 설계 단계나 성능 튜닝 단계에서 이루어집니다.
  • 왜 필요한가요? (조인 성능 병목 해결):
    • 정규화를 칼같이 적용해서 테이블을 수십 개로 쪼개놓으면 데이터는 중복 없이 깔끔해집니다.
    • 하지만 대규모 통계 리포트를 뽑거나 메인 대시보드 화면을 그리려면 이 수십 개의 테이블을 매번 JOIN 해야 합니다.
    • JOIN은 컴퓨터 컴퓨터 자원(CPU, 메모리)을 많이 소모하는 연산입니다. 사용자가 몰릴 때 이 조인 연산 때문에 화면이 멈추는 성능 병목이 생깁니다. 이때 조회 성능을 위해 테이블을 다시 조금 합치는 비정규화를 단행합니다.

② 아주 쉬운 비유

  • 자주 쓰는 스마트폰 충전기, 필기구, 안경을 매번 규칙대로 서랍 깊숙한 정리함에 정확히 넣어두면(정규화) 방은 깔끔하겠지만, 쓸 때마다 서랍을 여러 번 열어야 해서 너무 귀찮습니다.
  • 그래서 자주 쓰는 필수 물품 몇 개는 그냥 책상 위에 같이 올려두고 편리하게 바로 집어 쓰는 상태가 바로 비정규화입니다.

3. 한눈에 비교하는 정규화 vs 비정규화

구분정규화 (Normalization)비정규화 (Denormalization - 역정규화)
핵심 목표데이터 중복 제거, 데이터 일관성(무결성) 확보데이터 조회 속도 향상, 시스템 성능 최적화
주요 장점저장 공간 절약, 데이터 수정/삭제 시 안전성 보장복잡한 JOIN 연산 감소로 초고속 데이터 검색 가능
주요 단점테이블 개수가 늘어나 잦은 JOIN으로 조회 성능 저하 가능데이터 중복으로 인해 저장 공간 낭비 및 수정 시 오류 위험 증가
최적 시스템실시간 원장 거래가 빈번한 OLTP 시스템 (예: 은행 입출금)대규모 분석 리포트를 뽑아내는 OLAP / 데이터웨어하우스

실무 조언! (The Trade-off)

실무에서 "100% 완벽한 정규화"만을 고집하는 설계자는 좋은 평가를 받기 어렵습니다. 시스템의 성능과 안전성의 균형을 맞추는 것이 설계자의 진짜 역량입니다.

  1. 설계 초기: 기본적으로 정규화를 원칙으로 삼아 정교하게 쪼개는 것이 정석입니다. 처음부터 뭉뚱그려 설계하면 나중에 데이터가 꼬여 수습할 수 없습니다.
  2. 운영 및 최적화 단계: 실제 서비스를 운영하면서 조회 속도가 현저히 떨어지는 쿼리나 대시보드 연산이 발견되면, 그 부분만 콕 집어서 '비정규화(요약 테이블 별도 생성 등)'를 적용하여 성능을 끌어올리는 것이 현업 시니어 아키텍트들의 영리한 전략이랍니다!

SQL 명령어 종류 한눈에 보기 (요약 표)

분류명 (Abbreviation)한글 명칭역할 비유대표적인 명령어 (Commands)
DDL (Data Definition Language)데이터 정의어창고 뼈대 짓기 & 가구 배치CREATE, ALTER, DROP, TRUNCATE
DML (Data Manipulation Language)데이터 조작어서랍에 물건 넣고 꺼내기SELECT, INSERT, UPDATE, DELETE
DCL (Data Control Language)데이터 제어어보안요원 열쇠 권한 관리GRANT, REVOKE
TCL (Transaction Control Language)트랜잭션 제어어타임머신 및 최종 도장 찍기COMMIT, ROLLBACK, SAVEPOINT

1. DDL (Data Definition Language) - 데이터 정의어

"창고 건물을 설계하고 수납장을 제작하는 도구"

DDL은 데이터베이스의 구조나 틀(Schema)을 정의하고 변경하는 명령어들입니다. 테이블 안에 들어있는 알맹이(값)가 아니라, 데이터를 담는 수납장 자체의 외형을 만들고 부수는 역할을 합니다.

  • 주요 특징: DDL 명령을 실행하면 데이터베이스에 즉시 반영되며, 자동으로 커밋(Commit)되어 이전 상태로 되돌리기 어렵기 때문에 아주 신중하게 다뤄야 합니다.
  • 대표 명령어:
    • CREATE: 새로운 데이터베이스나 테이블, 뷰, 인덱스 등을 생성합니다.
    • ALTER: 기존 테이블의 컬럼을 추가/수정/삭제하는 등 구조를 변경합니다.
    • DROP: 테이블이나 데이터베이스 구조 자체를 완전히 철거(삭제)합니다.
    • TRUNCATE: 테이블의 뼈대(구조)는 그대로 남겨둔 채 안의 모든 데이터만 고속으로 전부 비웁니다.

2. DML (Data Manipulation Language) - 데이터 조작어

"서랍 속에 물건을 채우고, 꺼내서 요리하는 도구"

DML은 테이블에 저장되어 있는 실제 데이터(행/Row)를 다루는 명령어들입니다. 데이터 분석가나 AI 개발자가 실무에서 가장 많이 가공하고 쿼리를 작성하게 되는 메인 영역입니다.

  • 대표 명령어:
    • SELECT: 데이터베이스에서 원하는 조건의 데이터를 조회하고 검색합니다 (분석의 핵심!).
    • INSERT: 테이블에 새로운 데이터 행을 삽입하여 등록합니다.
    • UPDATE: 이미 저장되어 있는 특정 데이터 값을 수정합니다.
    • DELETE: 테이블에서 조건에 맞는 데이터를 선택하여 지웁니다.

3. DCL (Data Control Language) - 데이터 제어어

"창고 출입을 통제하는 보안 관리자의 마스터키"

데이터베이스는 기업의 기밀이나 사용자 정보가 담겨있는 안전구역입니다. DCL은 어떤 사용자에게 어떤 권한을 부여할지, 혹은 뺏을지 제어하여 시스템의 보안과 무결성을 유지합니다.

  • 대표 명령어:
    • GRANT: 특정 데이터베이스나 객체에 대한 접근 및 사용 권한을 특정 사용자에게 부여합니다.
    • REVOKE: 이전에 사용자에게 주었던 권한을 다시 안전하게 회수(취소)합니다.

4. TCL (Transaction Control Language) - 트랜잭션 제어어

"낙장불입을 보장하는 최종 승인 및 롤백 장치"

TCL은 이전에 다룬 '트랜잭션(더 이상 쪼갤 수 없는 작업 단위)'을 제어하는 명령어입니다. DML로 조작된 데이터의 변경 사항을 승인하거나 원상복구합니다.

  • 대표 명령어:
    • COMMIT: 임시로 실행한 모든 DML 작업 결과가 완벽할 때, 이를 데이터베이스에 영구적으로 확정 저장합니다.
    • ROLLBACK: 작업 도중 예기치 못한 에러가 나거나 수정이 잘못되었을 때, 트랜잭션 시작 이전의 상태로 되돌립니다.
    • SAVEPOINT: 중간중간 북마크(깃발)를 꽂아두어, 문제가 생겼을 때 맨 처음이 아닌 지정한 중간 지점으로만 롤백할 수 있게 도와줍니다.

금융이나 온라인 서비스 등 거의 모든 비즈니스 시스템에서 데이터의 신뢰성을 지켜내는 기능이 바로 트랜잭션(Transaction)입니다.

MySQL의 트랜잭션(Transaction) 정리

1. 트랜잭션(Transaction)이란 무엇인가요?

  • 정의: 트랜잭션의 사전적 의미는 '거래'이며, 컴퓨터 과학에서는 "더 이상 쪼갤 수 없는 업무 처리의 최소 단위"를 뜻합니다.
  • 핵심 개념: 하나의 목표를 이루기 위해 한꺼번에 수행되어야 할 일련의 연산 모음입니다.

3초 만에 이해하는 은행 송금 비유

여러분이 친구에게 계좌이체로 1만 원을 송금하는 상황을 가정해 봅시다. 이 작업은 컴퓨터 내부에서 크게 두 단계로 쪼개집니다.
1. 내 계좌에서 1만 원 인출
2. 친구 계좌에 1만 원 입금

만약 시스템 장애로 1번(인출)은 성공했는데, 2번(입금)이 실패했다면 어떻게 될까요?
내 돈 1만 원은 공중으로 날아가 버리고 엄청난 민원이 발생하겠죠. 따라서 이 두 단계는 "둘 다 아주 깔끔하게 성공(All)하거나, 아니면 애초에 아무 일도 없었던 것처럼 둘 다 취소(Nothing)"되어야만 안전합니다. 이 두 과정을 자석처럼 하나로 꽁꽁 묶어주는 기술이 바로 트랜잭션입니다.


2. 트랜잭션의 4가지 핵심 특징 (ACID)

안전한 데이터 거래를 보장하기 위해 트랜잭션은 ACID라고 불리는 4가지 전제 조건을 완벽히 만족해야 합니다.

  • 원자성 (Atomicity) 🔴
    • 트랜잭션 내의 모든 명령은 100% 모두 반영되거나, 아니면 0% 전혀 반영되지 않아야(All or Nothing) 합니다.
    • 어설프게 중간 단계만 성공한 상태로 데이터베이스에 남아있으면 안 됩니다.
  • 일관성 (Consistency) 🟢
    • 트랜잭션의 작업 처리 결과는 언제나 데이터베이스의 규칙을 위반하지 않고 일관성을 유지해야 합니다.
    • 트랜잭션 도중 데이터베이스가 바뀌더라도, 이미 시작된 트랜잭션은 처음에 참조했던 일관된 데이터 상태를 기반으로 끝까지 진행됩니다.
  • 독립성 (Isolation) 🔵
    • 동시에 실행되는 여러 개의 트랜잭션들은 서로의 연산에 절대로 끼어들거나 간섭할 수 없습니다.
    • 한 트랜잭션이 완전히 끝날 때(Commit)까지, 다른 트랜잭션은 진행 중인 임시 결과를 훔쳐보거나 참조할 수 없습니다.
  • 영구성 (Durability) 🟡
    • 트랜잭션이 성공적으로 완료(Commit)되었다면, 그 결과는 컴퓨터가 꺼지거나 데이터베이스 서버가 다운되더라도 영구적으로 안전하게 기록되어야 합니다.

3. 트랜잭션 제어를 위한 핵심 3대 명령어 (TCL)

MySQL에서 수동으로 트랜잭션을 조율할 때 사용하는 핵심 명령어입니다.

  • START TRANSACTION
    • "지금부터 내리는 명령어들은 하나로 묶어서 처리해줘!" 하고 트랜잭션의 시작을 알리는 신호탄입니다.
  • COMMIT (확정 저장)
    • "모든 작업이 에러 없이 완벽해! 지금까지의 변경 사항을 진짜 데이터베이스에 영구적으로 반영해줘!" 하고 승인하는 명령입니다.
    • COMMIT이 떨어지면 비로소 하나의 트랜잭션이 안전하게 종료됩니다.
  • ROLLBACK (취소 및 원상복구)
    • "앗, 중간에 예상치 못한 에러가 발생했어! 트랜잭션 시작하기 전 상태로 전부 되돌려줘!" 하고 모든 수정을 취소하는 명령어입니다.

4. 트랜잭션의 5가지 상태 흐름 (State)

트랜잭션은 시작되어 끝날 때까지 다음과 같은 단계별 상태를 거치며 생명주기를 가집니다.

  [ 활성 (Active) ] ➡️ 정상 실행 중
         ⬇️
  [ 부분 완료 (Partially Committed) ] ➡️ 마지막 연산 종료 후 Commit 대기
         ⬇️
  [ 완료 (Committed) ] ➡️ Commit 성공! DB 영구 반영 완료

(오류 발생 시)

  [ 활성 (Active) ] ➡️ 실행 중 오류 발생
         ⬇️
  [ 실패 (Failed) ] ➡️ 연산 중단 상태
         ⬇️
  [ 철회 (Aborted) ] ➡️ Rollback 연산 수행하여 실행 전으로 원상복구

실무 초특급 꿀팁 & 주의사항!

  1. 모든 명령어가 롤백되는 것은 아닙니다!
    • INSERT, UPDATE, DELETE 같은 DML(데이터 조작어)은 에러가 나면 롤백이 가능합니다.
    • 하지만 CREATE, DROP, ALTER, TRUNCATE 같은 DDL(데이터 정의어) 문법은 트랜잭션 실행 중에 에러가 나서 롤백을 선언하더라도 롤백 대상이 되지 않고 즉시 반영되어 버리니 아주 주의하셔야 합니다!
  2. 데이터베이스 엔진을 꼭 확인하세요!
    • MySQL/MariaDB에서는 스토리지 엔진이 InnoDB로 설정되어 있어야만 이 강력한 트랜잭션 기능을 정상적으로 사용할 수 있습니다. 구버전이나 MyISAM 엔진을 쓰면 트랜잭션이 작동하지 않으니 개발 시작 전 꼭 설계 설정을 체크해 보세요!

오늘 배운 트랜잭션 30초 요약!

트랜잭션은 계좌이체처럼 더 이상 쪼갤 수 없는 하나의 작업 단위이며, 데이터의 안전을 위해 ACID 특징을 지키고, 문제가 생기면 ROLLBACK으로 깔끔하게 원상복구하고 성공 시에만 COMMIT으로 저장하는 안전장치입니다.

dbms 마직말 날...

select
b.name as 구매자 명,
p.product_name as 상품명,
p.price as 단가,
pc.guantity as 구매수량,
(p.price * pc.guantity) as 총결제금액
from purchases as pc
inner join buyers as b
on pc.buyers as b
inner join products as p
on pc.product_id = p.product_id;

< 위 명령어의 처리 흐름 단계별 분석 :작동 원리>
컴퓨터가 이 쿼리를 실행하는 내부적인 단계(Execution Order)를 시각적으로 따라가 봅시다.
1단계: FROM purchases AS pc (징검다리 테이블 먼저 놓기)
가장 먼저 활동 기록이 담긴 핵심 영수증 장부(purchases)를 테이블 위에 올려놓습니다. 컴퓨터는 이 테이블을 아주 짧게 pc라는 별칭으로 부르기 시작합니다.
2단계: INNER JOIN buyers AS b ON pc.buyer_id = b.buyer_id (첫 번째 섬 연결)
영수증 장부(pc)에 적힌 고객번호(buyer_id)를 단서로 삼아, 고객 명부 테이블(buyers, 약칭 b)로 찾아갑니다.
고객번호가 똑같은 행끼리 가로로 착 이어 붙입니다. 이 단계를 거치면 이제 숫자로만 되어 있던 고객번호 옆에 '김철수', '이영희' 같은 실제 이름이 생겨납니다.
3단계: INNER JOIN products AS p ON pc.product_id = p.product_id (두 번째 섬 연결)
영수증 장부(pc)에 적힌 상품번호(product_id)를 단서로 삼아, 상품 목록 테이블(products, 약칭 p)로 찾아갑니다.
상품번호가 일치하는 행을 기존에 완성된 큰 표 오른쪽에 추가로 이어 붙입니다. 이제 숫자로만 되어 있던 상품번호 옆에 '아이패드', '마우스' 같은 실제 상품명과 가격 정보가 추가됩니다.
4단계: SELECT ... (원하는 컬럼만 선택하고 계산하기)
3개의 테이블이 완벽하게 가로로 합쳐진 거대한 임시 테이블에서, 우리가 처음에 요청한 구매자명(b.name), 상품명(p.product_name), 단가(p.price), 수량(pc.quantity) 컬럼만 콕 집어서 꺼냅니다.
동시에 p.price * pc.quantity를 계산하여 총결제금액이라는 새로운 결과 컬럼을 만들어 최종 화면에 보여줍니다.

0개의 댓글