MySQL ‐ Learning Data Modeling Through Practical Examples - thought-corner/Backend-PlayGround GitHub Wiki
MySQL - Learning Data Modeling Through Practical Examples
Data Modeling: Logical Design for Boolean Attributes
- 논리 모델링에서 Boolean(True/False) 타입은 양날의 검과 같다.
- 비즈니스 요구사항이 정말 단순한 Yes/No로 완벽하게 설명될 때는 최고의 선택이지만, 복잡한 개념을 억지로 압축하려고 할 때는 미래의 기술 부채(Technical Debt)가 된다.
🟢 단순 Yes/No라면 쓰는 것이 좋다(Good Cases)
- 비즈니스 로직상 더 이상의 부가 정보가 절대 필요 없는 순수한 상태나 플래그(Flag)일 때는 Boolean이 가장 직관적이고 효율적이다.
is_deleted(논리적 삭제 여부) : 데이터베이스에서 실제로 레코드를 지우지 않고 숨김 처리만 할 때 사용한다. "삭제되었는가?"에 대한 답은 철저하게 O/X로 나뉜다.is_email_verified(이메일 인증 여부) : 인증을 했거나, 안 했거나 둘 중 하나이다.is_default_address(기본 배송지 여부) : 여러 배송지 중 기본 배송지인지 판별하는 순수한 논리적 플래그이다.
- 이런 경우 Boolean 속성은 모델을 단순하게 유지해 주고 쿼리의 가독성을 높여주는 아주 좋은 설계이다.
🔴 함축적으로 가져가면 안 좋은 경우(Bad Cases)
- 현재는 Yes/No처럼 보이지만, 속성 내부에 더 복잡한 의미나 '확장 가능성'이 숨어있는 경우이다.
has_discount(할인 적용 여부) : 처음 기획할 때는 "이 상품 할인해?"라는 질문에서 시작하여 Boolean으로 만들기 쉽다. 하지만 서비스가 성장하면 필연적으로 "할인율은 몇 %야?", "할인 기간은 언제까지야?", "쿠폰 할인이야, 정률 할인이야?" 같은 요구사항이 붙는다. 이 경우 Boolean 대신 '할인(Discount)'이라는 별도 테이블이나 더 구체적인 속성들로 도출했어야 한다.is_premium_member(프리미엄 회원 여부) : "유료 회원인가요?"라는 질문엔 Yes/No가 맞지만, 나중에 '골드', '플래티넘' 같은 등급(Tier)이 추가되는 순간 이 컬럼은 쓸모가 없어진다. 처음부터membership_level(VARCHAR/ENUM 등)이나 별도의 등급 엔티티로 설계하는 것이 안전하다.has_wifi(와이파이 유무) : 식당 정보에서 has_wifi가 True라고 해보자. 사용자가 "비밀번호는 뭔가요?"라고 묻는다면 이 데이터 모델은 대답할 수 없다.
Data Modeling: Physical Design Strategy – One Table per Anchor
CREATE TABLE restaurants (
restaurant_id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
address VARCHAR(200),
phone VARCHAR(20),
has_parking BOOLEAN DEFAULT FALSE,
has_wifi BOOLEAN DEFAULT FALSE,
has_delivery BOOLEAN DEFAULT FALSE,
is_vegetarian_friendly BOOLEAN DEFAULT FALSE,
has_outdoor_seating BOOLEAN DEFAULT FALSE,
has_live_music BOOLEAN DEFAULT FALSE,
accepts_credit_card BOOLEAN DEFAULT FALSE,
is_24_hours BOOLEAN DEFAULT FALSE
has_rooftop BOOLEAN DEFAULT FALSE
);
- 장점 :
JOIN필요 없고 별도 테이블이 필요 없다. 초기 개발 단계에서는 굉장히 큰 장점을 가진다. - 단점 : 유지보수 및 확장성 관점에서는 굉장히 불리하다.
Data Modeling: Physical Design Strategy – Side Tables
-- 메인 테이블: 핵심 비즈니스 정보만 포함
CREATE TABLE restaurants (
restaurant_id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
address VARCHAR(200),
phone VARCHAR(20),
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Side Table: Boolean 속성들을 별도 테이블로 분리
CREATE TABLE restaurant_flags (
restaurant_id INT PRIMARY KEY,
has_parking BOOLEAN DEFAULT FALSE,
has_wifi BOOLEAN DEFAULT FALSE,
has_delivery BOOLEAN DEFAULT FALSE,
is_vegetarian_friendly BOOLEAN DEFAULT FALSE,
has_outdoor_seating BOOLEAN DEFAULT FALSE,
has_live_music BOOLEAN DEFAULT FALSE,
accepts_credit_card BOOLEAN DEFAULT FALSE,
is_24_hours BOOLEAN DEFAULT FALSE,
FOREIGN KEY (restaurant_id) REFERENCES restaurants(restaurant_id)
);
-- @Transactional(스프링 생태계에서 사용되는 선언적 어노테이션)
begin;
UPDATE restaurants SET name = 'New Name' WHERE restaurant_id = 1;
UPDATE restaurant_flags SET has_parking = true WHERE restaurant_id = 1;
commit;
- 장점 : 메인 테이블의 경량화 및 I/O 성능 향상, 메인 테이블 잠금 및 장애 리스크 감소, 확고한 데이터 정합성 보장
- 단점 : 조회 시 필연적인 JOIN 오버헤드, 근본적인 스키마 유연성 문제 미해결, 애플리케이션 로직의 복잡도 증가
Data Modeling: Physical Design Strategy – Entity-Attribute-Value (EAV)
-- 메인 엔티티 테이블
CREATE TABLE restaurants (
restaurant_id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
address VARCHAR(200),
phone VARCHAR(20),
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- EAV 테이블: 속성을 행으로 저장
CREATE TABLE restaurant_attributes (
restaurant_id INT NOT NULL,
attr_name VARCHAR(50) NOT NULL,
attr_value VARCHAR(100),
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (restaurant_id, attr_name),
FOREIGN KEY (restaurant_id) REFERENCES restaurants(restaurant_id)
);
INSERT INTO restaurant_attributes (restaurant_id, attr_name, attr_value) VALUE (1, 'test', 'true');
INSERT INTO restaurant_attributes (restaurant_id, attr_name, attr_value) VALUE (1, 'has_parking', 'true');
- 장점 : 무한한 확장성과 궁극의 유연성을 가진다.
- 단점 : 데이터 정합성 파괴 가능성이 매우 높으며 쿼리 지옥과 성능 저하를 유발한다.
Database Design Deep Dive: Schema Evolution
COPY: 기존 방식으로 테이블을 복사하는 방식INPLACE: 인덱스 재구성하는 방식DEFAULT: MySQL이 자동으로 선택NONE: 락 없이 실행SHARED: 공유 락을 사용EXCLUSIVE: 배타 락을 사용DEFAULT: MySQL이 자동으로 선택
-- 1. 데이터 타입 변경
ALTER TABLE restaurants
MODIFY COLUMN name VARCHAR(200),
ALGORITHM=COPY, LOCK=EXCLUSIVE;
-- 2. 컬럼 이름 변경
ALTER TABLE restaurants
CHANGE COLUMN name restaurant_name VARCHAR(100),
ALGORITHM=COPY, LOCK=EXCLUSIVE;
❗ALTER TABLE 작업이 긴 다운타임을 유발하는 이유 : 기존 테이블을 복사하고 새로운 테이블을 만드는 과정에서 독점 잠금을 사용하여 다른 모든 작업을 차단해 서비스 다운타임을 발생시키기 때문이다.
Database Design Deep Dive: Modern Alternatives
-- MySQL JSON 테이블 생성
CREATE TABLE restaurants (
restaurant_id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
attributes JSON
);
-- JSON 데이터 삽입
INSERT INTO restaurants (restaurant_id, name, attributes) VALUES
(1, 'Pizza Palace', '{"has_parking": true, "has_wifi": true}'),
(2, 'Burger King', '{"has_parking": false, "has_wifi": true}');
- 장점 : 스키마 리스(schema-less) 유연성, 데이터 타입의 엄격한 보존(JSON 타입 그대로), 비정형 데이터의 효율적 관리, 강력한 JSON 내장 함수 지원
- 단점 : 데이터 무결성 제약 부재, 검색 성능 한계(인덱스를 걸기 어려움), 통계 및 집계 어려움, 저장 공간 오버헤드