본문 바로가기
IT정보

데이터베이스 설계 원칙 가이드

by 오늘의 테크 2025. 8. 16.

개요

데이터베이스 설계는 효율적이고 안정적인 정보 시스템의 핵심 요소입니다. 올바른 설계 원칙을 적용하면 데이터의 무결성을 보장하고, 성능을 최적화하며, 유지보수성을 향상시킬 수 있습니다.

데이터베이스 설계의 기본 목표

1. 데이터 무결성 보장

  • 개체 무결성: 기본키는 NULL 값을 가질 수 없고 유일해야 함
  • 참조 무결성: 외래키 값은 참조하는 테이블의 기본키 값과 일치해야 함
  • 도메인 무결성: 속성 값은 정의된 도메인 범위 내에서만 존재해야 함
  • 사용자 정의 무결성: 비즈니스 규칙에 따른 제약조건 만족

2. 데이터 중복 최소화

  • 저장 공간 효율성 향상
  • 데이터 일관성 유지
  • 업데이트 이상 현상 방지

3. 성능 최적화

  • 빠른 데이터 조회 및 처리
  • 효율적인 인덱스 설계
  • 최적화된 쿼리 실행

4. 확장성 및 유연성

  • 비즈니스 요구사항 변화에 대응
  • 시스템 성장에 따른 확장 가능
  • 새로운 기능 추가 용이성

정규화 (Normalization)

정규화는 데이터 중복을 제거하고 데이터 무결성을 보장하기 위한 체계적인 접근 방법입니다.

제1정규형 (1NF)

규칙: 모든 속성은 원자값(Atomic Value)을 가져야 합니다.

위반 사례:

고객ID | 이름   | 전화번호
001   | 김철수 | 010-1234-5678, 02-987-6543

정규화 후:

고객ID | 이름   | 전화번호유형 | 전화번호
001   | 김철수 | 휴대폰      | 010-1234-5678
001   | 김철수 | 집전화      | 02-987-6543

제2정규형 (2NF)

규칙: 1NF를 만족하고, 기본키가 아닌 모든 속성이 기본키에 완전 함수적 종속되어야 합니다.

위반 사례:

주문ID | 상품ID | 상품명   | 수량 | 상품가격
001   | P001  | 노트북   | 2   | 1000000
001   | P002  | 마우스   | 1   | 50000

상품명과 상품가격은 상품ID에만 종속됨 (부분 함수적 종속)

정규화 후:

  • 주문상세 테이블: 주문ID, 상품ID, 수량
  • 상품 테이블: 상품ID, 상품명, 상품가격

제3정규형 (3NF)

규칙: 2NF를 만족하고, 기본키가 아닌 속성들 간에 이행적 함수 종속이 없어야 합니다.

위반 사례:

학생ID | 학과코드 | 학과명
S001  | CS001   | 컴퓨터과학과
S002  | CS001   | 컴퓨터과학과

학과명은 학생ID → 학과코드 → 학과명으로 이행적 종속

정규화 후:

  • 학생 테이블: 학생ID, 학과코드
  • 학과 테이블: 학과코드, 학과명

BCNF (Boyce-Codd Normal Form)

규칙: 3NF를 만족하고, 모든 결정자가 후보키여야 합니다.

제4정규형 (4NF) 및 제5정규형 (5NF)

다중값 종속과 조인 종속을 제거하여 더 높은 수준의 정규화를 달성합니다.

반정규화 (Denormalization)

반정규화의 필요성

정규화가 항상 최적은 아닙니다. 다음과 같은 경우 반정규화를 고려합니다:

  • 성능 향상이 필요한 경우
  • 복잡한 조인 연산을 단순화해야 하는 경우
  • 읽기 위주의 워크로드
  • 실시간 처리가 중요한 시스템

반정규화 기법

1. 테이블 통합

자주 함께 조회되는 테이블들을 통합하여 조인 비용을 줄입니다.

2. 컬럼 추가

계산된 값이나 집계 값을 미리 저장하는 컬럼을 추가합니다.

예시:

-- 정규화된 구조
Orders: 주문ID, 고객ID, 주문날짜
OrderItems: 주문ID, 상품ID, 수량, 단가

-- 반정규화: 주문 총액 컬럼 추가
Orders: 주문ID, 고객ID, 주문날짜, 총액

3. 중복 허용

성능 향상을 위해 의도적으로 데이터 중복을 허용합니다.

반정규화 시 고려사항

  • 데이터 일관성 유지 방안
  • 업데이트 로직의 복잡성 증가
  • 저장 공간 증가
  • 동시성 제어 문제

인덱스 설계

인덱스의 종류

1. 클러스터형 인덱스 (Clustered Index)

  • 테이블당 하나만 존재
  • 물리적 데이터 순서를 결정
  • 기본키에 자동으로 생성

2. 비클러스터형 인덱스 (Non-Clustered Index)

  • 테이블당 여러 개 생성 가능
  • 논리적 순서만 제공
  • 포인터를 통해 실제 데이터 접근

3. 복합 인덱스 (Composite Index)

여러 컬럼을 결합한 인덱스

CREATE INDEX idx_customer_order 
ON Orders (고객ID, 주문날짜);

4. 커버링 인덱스 (Covering Index)

쿼리에 필요한 모든 컬럼을 포함한 인덱스

인덱스 설계 원칙

1. 선택성이 높은 컬럼 우선

-- 좋은 예: 주민등록번호 (선택성 높음)
CREATE INDEX idx_ssn ON Customer (주민등록번호);

-- 나쁜 예: 성별 (선택성 낮음)
CREATE INDEX idx_gender ON Customer (성별);

2. 복합 인덱스 컬럼 순서

가장 선택성이 높고 자주 사용되는 컬럼을 앞에 배치

-- WHERE 고객ID = ? AND 주문날짜 BETWEEN ? AND ?
CREATE INDEX idx_order_search 
ON Orders (고객ID, 주문날짜);

3. 인덱스 유지 비용 고려

  • INSERT, UPDATE, DELETE 시 인덱스도 함께 업데이트
  • 너무 많은 인덱스는 DML 성능 저하
  • 사용하지 않는 인덱스 정기적 제거

데이터 타입 선택

숫자 타입

정수 타입

-- 작은 범위: TINYINT (0~255)
age TINYINT UNSIGNED

-- 일반적인 ID: INT (약 21억)
customer_id INT UNSIGNED AUTO_INCREMENT

-- 큰 범위: BIGINT
transaction_amount BIGINT

실수 타입

-- 정확한 소수점: DECIMAL
price DECIMAL(10,2)  -- 총 10자리, 소수점 2자리

-- 근사값: FLOAT, DOUBLE
coordinate_lat DOUBLE

문자 타입

고정 길이 vs 가변 길이

-- 고정 길이: 항상 같은 크기 (국가코드, 성별 등)
country_code CHAR(2)
gender CHAR(1)

-- 가변 길이: 길이가 다양함 (이름, 주소 등)
customer_name VARCHAR(100)
address VARCHAR(500)

텍스트 타입

-- 작은 텍스트: TEXT (65KB)
description TEXT

-- 큰 텍스트: LONGTEXT (4GB)
article_content LONGTEXT

날짜/시간 타입

-- 날짜만: DATE
birth_date DATE

-- 날짜와 시간: DATETIME
created_at DATETIME

-- 타임스탬프: TIMESTAMP (시간대 자동 변환)
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 
           ON UPDATE CURRENT_TIMESTAMP

제약조건 설계

기본키 (Primary Key)

-- 단일 컬럼 기본키
CREATE TABLE Customer (
    customer_id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

-- 복합 기본키
CREATE TABLE OrderItem (
    order_id INT,
    item_id INT,
    quantity INT,
    PRIMARY KEY (order_id, item_id)
);

외래키 (Foreign Key)

CREATE TABLE Orders (
    order_id INT PRIMARY KEY,
    customer_id INT,
    FOREIGN KEY (customer_id) 
        REFERENCES Customer(customer_id)
        ON DELETE RESTRICT
        ON UPDATE CASCADE
);

참조 무결성 옵션

  • RESTRICT: 참조되는 행 삭제/수정 금지
  • CASCADE: 연쇄 삭제/수정
  • SET NULL: NULL로 설정
  • SET DEFAULT: 기본값으로 설정

체크 제약조건 (Check Constraint)

CREATE TABLE Product (
    product_id INT PRIMARY KEY,
    price DECIMAL(10,2) CHECK (price >= 0),
    category VARCHAR(50) CHECK (category IN ('전자제품', '의류', '도서'))
);

고유 제약조건 (Unique Constraint)

CREATE TABLE Customer (
    customer_id INT PRIMARY KEY,
    email VARCHAR(100) UNIQUE,
    phone VARCHAR(20),
    CONSTRAINT uk_customer_phone UNIQUE (phone)
);

성능 최적화 원칙

쿼리 최적화

1. SELECT 절 최적화

-- 나쁜 예: 모든 컬럼 조회
SELECT * FROM Customer WHERE city = '서울';

-- 좋은 예: 필요한 컬럼만 조회
SELECT customer_id, name, email 
FROM Customer WHERE city = '서울';

2. WHERE 절 최적화

-- 나쁜 예: 함수 사용으로 인덱스 무효화
SELECT * FROM Orders 
WHERE YEAR(order_date) = 2024;

-- 좋은 예: 범위 조건으로 인덱스 활용
SELECT * FROM Orders 
WHERE order_date >= '2024-01-01' 
  AND order_date < '2025-01-01';

3. JOIN 최적화

-- INNER JOIN 사용 (불필요한 데이터 제외)
SELECT c.name, o.order_date
FROM Customer c
INNER JOIN Orders o ON c.customer_id = o.customer_id
WHERE c.city = '서울';

-- 조인 순서 고려 (작은 테이블 먼저)

테이블 파티셔닝

수평 파티셔닝 (Horizontal Partitioning)

-- 날짜별 파티셔닝
CREATE TABLE Sales (
    sale_id INT,
    sale_date DATE,
    amount DECIMAL(10,2)
) PARTITION BY RANGE (YEAR(sale_date)) (
    PARTITION p2022 VALUES LESS THAN (2023),
    PARTITION p2023 VALUES LESS THAN (2024),
    PARTITION p2024 VALUES LESS THAN (2025)
);

수직 파티셔닝 (Vertical Partitioning)

자주 사용되는 컬럼과 그렇지 않은 컬럼을 분리

-- 기본 정보 테이블
Customer_Basic: customer_id, name, email

-- 상세 정보 테이블  
Customer_Detail: customer_id, address, phone, notes

보안 설계 원칙

1. 최소 권한 원칙

-- 애플리케이션별 전용 사용자 생성
CREATE USER 'app_read'@'%' IDENTIFIED BY 'strong_password';
GRANT SELECT ON shop.* TO 'app_read'@'%';

CREATE USER 'app_write'@'%' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE ON shop.orders TO 'app_write'@'%';

2. 민감한 데이터 암호화

-- 개인정보 암호화 저장
CREATE TABLE Customer (
    customer_id INT PRIMARY KEY,
    name VARCHAR(100),
    ssn_encrypted VARBINARY(256),  -- 주민등록번호 암호화
    email_hash VARCHAR(64)         -- 이메일 해시값
);

3. 감사 로그 (Audit Log)

CREATE TABLE audit_log (
    log_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    table_name VARCHAR(50),
    operation VARCHAR(10),  -- INSERT, UPDATE, DELETE
    user_id VARCHAR(50),
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    old_values JSON,
    new_values JSON
);

데이터베이스 설계 프로세스

1. 요구사항 분석

  • 기능 요구사항: 시스템이 수행해야 할 기능
  • 비기능 요구사항: 성능, 보안, 가용성 등
  • 데이터 요구사항: 저장해야 할 데이터의 종류와 특성

2. 개념적 설계 (Conceptual Design)

  • ERD (Entity Relationship Diagram) 작성
  • 개체, 속성, 관계 식별
  • 비즈니스 규칙 정의

3. 논리적 설계 (Logical Design)

  • 관계형 모델로 변환
  • 정규화 수행
  • 무결성 제약조건 정의

4. 물리적 설계 (Physical Design)

  • 테이블, 인덱스 생성
  • 저장 구조 결정
  • 성능 최적화

5. 구현 및 튜닝

  • DDL 작성 및 실행
  • 성능 테스트
  • 지속적인 모니터링 및 최적화

최신 트렌드와 고려사항

NoSQL과의 통합

  • 폴리글랏 퍼시스턴스: 데이터 특성에 맞는 DB 선택
  • CQRS (Command Query Responsibility Segregation): 읽기/쓰기 분리
  • 이벤트 소싱: 상태 변화를 이벤트로 저장

클라우드 데이터베이스

  • 자동 스케일링
  • 관리형 서비스 활용
  • 백업 및 복구 자동화

마이크로서비스 아키텍처

  • 데이터베이스 서비스별 분리
  • API를 통한 데이터 접근
  • 이벤트 기반 데이터 동기화

결론

효과적인 데이터베이스 설계는 비즈니스 요구사항과 기술적 제약사항 사이의 균형을 찾는 것입니다. 정규화를 통해 데이터 무결성을 보장하되, 성능이 필요한 부분에서는 적절한 반정규화를 적용해야 합니다.

또한 설계 단계에서부터 확장성과 유지보수성을 고려하여, 향후 비즈니스 변화에 유연하게 대응할 수 있는 구조를 만드는 것이 중요합니다. 지속적인 모니터링과 최적화를 통해 시스템의 성능을 유지하고 개선해 나가야 합니다.