데이터베이스를 설계할 때 가장 먼저 하는 작업은 테이블을 정의하는 것이다. 이때 반드시 함께 고려해야 할 것이 데이터 타입과 제약 조건이다. 이 두 요소는 단순히 문법 문제가 아니라, 시스템의 데이터 무결성과 구조 안정성, 심지어 성능까지도 좌우하는 핵심이다.
1. 데이터 타입: 데이터를 어떤 형식으로 저장할 것인가?
1-1. 데이터 타입이 중요한 이유
데이터 타입은 각 컬럼이 어떤 종류의 값을 가질 수 있는지를 정의한다. 즉, 숫자인가, 문자열인가, 날짜인가를 명시하며, 이를 통해 DBMS는 저장 공간을 확보하고 연산 방식도 결정한다. 만약 적절하지 않은 타입을 설정하면 다음과 같은 문제가 생긴다
- 불필요한 저장 공간 낭비
- 정렬, 필터링 등에서 비효율 발생
- 타입 변환 오류로 인해 예외 발생 가능
- 성능 저하 또는 데이터 불일치
1-2. 주요 데이터 타입 종류
- 정수형 (INT, BIGINT, SMALLINT, TINYINT 등)
- 정수값 저장용
- 크기 차이는 저장 가능한 범위와 공간 차이 때문
- INT: 일반적인 정수 (약 ±21억)
- BIGINT: 매우 큰 수 (±9경) 필요 시 사용
- 실수형 (FLOAT, DOUBLE, DECIMAL)
- 실수 또는 소수점 숫자
- FLOAT, DOUBLE은 근사값(정밀도 손실 있음), DECIMAL은 정확한 수치(금액 등에서 권장)
- 문자열형 (CHAR, VARCHAR, TEXT)
- CHAR(n): 고정 길이 문자열
- VARCHAR(n): 가변 길이 문자열 (일반적으로 가장 많이 사용)
- TEXT: 긴 문자열 저장용 (제한 있음, 인덱싱 불가 또는 불편)
- 날짜형 (DATE, DATETIME, TIMESTAMP, TIME, YEAR)
- DATE: 연-월-일
- DATETIME: 시각 포함
- TIMESTAMP: 자동 시간 기록(로그 기록 등에서 유용)
- 불리언형 (BOOLEAN 또는 TINYINT(1))
- TRUE / FALSE를 표현할 때 사용
- MySQL에서는 실제로는 TINYINT(1)로 동작
2. 제약 조건: 데이터의 무결성을 보장하는 수단
2-1. 제약 조건이 필요한 이유
데이터가 데이터답게 동작하려면 단순히 저장하는 것만으론 부족하다. 값의 중복을 막고, 반드시 있어야 할 값을 강제하고, 관계를 보장해야 한다. 이런 역할을 하는 것이 제약 조건(Constraint)이다. 잘 정의된 제약 조건은 다음을 가능하게 한다.
- 불필요한 중복 데이터 방지
- 필수 정보 누락 차단
- 관계형 데이터 간 일관성 유지
- 코드 없이 데이터 보호
2-2. 주요 제약 조건 종류
- PRIMARY KEY (기본 키)
- 테이블의 고유한 식별자
- 반드시 NOT NULL + UNIQUE
- 한 테이블에 하나만 존재 가능
- UNIQUE
- 중복 불가. NULL은 허용됨
- 이메일, 주민번호, 전화번호 등에 사용
- NOT NULL
- 반드시 값이 있어야 하는 컬럼
- 사용자명, 비밀번호, 주문일시 등
- DEFAULT
- 값을 입력하지 않을 경우 자동으로 지정되는 기본값
- 상태값, 등록일 등
- FOREIGN KEY (외래 키)
- 다른 테이블의 기본 키를 참조
- 테이블 간 관계를 명시적으로 표현
- ON DELETE / ON UPDATE 옵션으로 연관 동작 설정 가능
3. 제약 조건 설계 시 주의사항
3-1. 복합 키의 사용
PRIMARY KEY (col1, col2)와 같이 두 개 이상의 컬럼을 묶어 기본 키로 설정하는 경우도 있다. 복합 키는 조합의 유일성을 보장할 때 사용하지만, 불필요하게 사용하면 인덱스 최적화에 어려움이 생기므로 신중하게 고려해야 한다.
3-2. NULL과 UNIQUE의 관계
UNIQUE는 NULL을 허용하지만, NULL 값은 중복으로 간주되지 않는다. 즉, 같은 컬럼에 NULL이 여러 개 들어가도 위반되지 않음. 따라서 NULL을 허용하지 않으면서 중복도 막으려면 UNIQUE + NOT NULL을 동시에 설정해야 한다.
3-3. 외래 키의 성능 이슈
FOREIGN KEY는 데이터 무결성 보장에 강력하지만, JOIN, 삭제, 업데이트가 자주 발생하는 경우에는 성능 이슈 또는 트랜잭션 충돌을 일으킬 수 있다. MySQL에서는 성능을 이유로 외래 키를 아예 비활성화하고, 애플리케이션 레벨에서 관계를 관리하는 전략도 일부 시스템에서는 쓰인다. 즉, 실무에서는 FOREIGN KEY의 존재 여부를 트래픽/모델 복잡도에 따라 선택적으로 적용하기도 한다.
4. 실무 설계 전략: 타입과 제약 조건의 조합
4-1. 정수형 + AUTO_INCREMENT + PRIMARY KEY
- 거의 모든 테이블의 기본 구조
- 예: 회원번호, 게시글 번호 등
- id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY
4-2. 상태값 ENUM or TINYINT
- 상태 컬럼은 문자열보다는 숫자로 관리 (ex: status TINYINT DEFAULT 0)
- ENUM은 표현은 쉽지만, 변경/확장에 취약
4-3. 날짜 값에는 DEFAULT CURRENT_TIMESTAMP 고려
- 등록일 등은 DATETIME 또는 TIMESTAMP + DEFAULT CURRENT_TIMESTAMP 조합 사용
- 수정일은 ON UPDATE CURRENT_TIMESTAMP 함께 사용
4-4. 관계형 구조는 반드시 외래 키 고려
- 주문-회원, 게시글-작성자, 댓글-게시글 등 관계가 명확한 구조에서는 FOREIGN KEY + INDEX 조합을 활용해 무결성과 성능을 모두 챙길 수 있다
5. 잘못된 설계가 부르는 문제들
- 타입이 모호할 경우
- 예: 전화번호를 INT로 저장 → 010 누락
- 숫자지만 의미상 문자열(우편번호, 주민번호)은 VARCHAR가 정답
- 제약 조건이 없을 경우
- 비어 있는 이메일, 중복된 ID 등
- 데이터 정합성 붕괴, 검색 불능
- 기본값 미설정 시
- INSERT 시 누락 필드 발생 → 오류 또는 NULL 저장
- 외래 키 누락 시
- 삭제된 회원의 게시글이 계속 존재 → 데이터 고아 현상
6. 제약 조건의 실무 적용 전략
제약 조건은 단순한 선언이 아니라 업무 로직을 데이터 계층에서 먼저 방어하는 장치다. 개발자는 종종 유효성 검사를 애플리케이션에서만 처리하려 하지만, 데이터베이스 수준에서 검증이 병행되지 않으면 시스템 외부에서 직접 쿼리한 경우 무결성이 깨질 수 있다.
6-1. 유일성 보장은 반드시 UNIQUE로 처리
애플리케이션에서 중복 체크를 하더라도, UNIQUE 제약이 없다면 동시 요청으로 인해 중복 데이터가 발생할 수 있다.
- 예: 동시에 이메일 등록 요청 → 둘 다 insert 처리 → 중복 발생
이러한 문제는 UNIQUE 제약 조건 하나로 해결 가능하다. 즉, 데이터 검증의 최종 보루는 DB 제약 조건이다.
6-2. 기본값 설정은 선택이 아닌 필수
사용자가 직접 값을 입력하지 않아도 비즈니스 로직상 필요한 상태값, 시간값은 반드시 DEFAULT로 선언해야 한다.
예:
- status TINYINT DEFAULT 0 (0: 대기, 1: 완료)
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
- is_deleted TINYINT DEFAULT 0
기본값을 선언하지 않으면, INSERT 시 누락된 컬럼은 NULL로 들어갈 수 있고 이는 이후 쿼리 조건에서 누락되거나 UI에서 오류를 발생시킬 수 있다.
6-3. 외래 키의 ON DELETE / ON UPDATE 옵션
외래 키를 선언할 때는 반드시 아래 옵션들을 설정하는 습관을 들이는 것이 좋다:
- ON DELETE CASCADE: 부모가 삭제되면 자식도 삭제
- ON DELETE SET NULL: 부모가 삭제되면 자식 참조값 NULL로 변경
- ON UPDATE CASCADE: 부모 키 변경 시 자식도 자동 반영
이 설정을 하지 않으면 부모 데이터를 삭제할 수 없게 되거나, 참조 무결성이 깨진다.
7. 데이터 타입 선택 실수 사례
데이터 타입은 익숙한 타입으로 습관처럼 선택하는 경우가 많지만, 이로 인해 공간 낭비, 정렬 오류, 데이터 손실이 발생할 수 있다.
7-1. 전화번호를 정수로 저장
전화번호는 숫자로만 구성돼 있지만, INT로 저장하면 앞자리가 0인 경우 잘려버리는 치명적 오류가 발생한다.
-- 잘못된 설계
phone_number INT → 01012345678 → 1012345678로 저장됨
해결책은 VARCHAR(13) 또는 CHAR(11) 타입으로 저장하는 것이다. 숫자처럼 보이지만 계산하지 않을 값은 문자열로 저장하는 것이 정답이다.
7-2. 금액을 FLOAT으로 저장
FLOAT, DOUBLE은 실수형이지만 정밀도 손실이 발생할 수 있기 때문에 금액 계산에는 부적절하다. 정확한 금액 계산에는 DECIMAL(10, 2)과 같은 타입이 필수이며, 특히 금융 서비스에서는 정밀 오차가 실제 결제 오류로 이어질 수 있다.
7-3. 날짜를 문자열로 저장
날짜를 VARCHAR로 저장하면 정렬, 필터링, 범위 조건 등에서 전혀 활용할 수 없게 된다. 또한 '2023-1-1', '2023-01-01', '20230101' 등 포맷도 제각각일 수 있어 쿼리나 로직이 복잡해진다. 날짜는 반드시 DATE, DATETIME, TIMESTAMP 등 전용 타입으로 저장해야 한다.
8. 데이터 설계와 확장성 고려
좋은 테이블 설계는 현재만이 아니라 미래의 구조 확장까지 고려해야 한다. 데이터 타입과 제약 조건은 이 확장 가능성을 높이거나 제한하는 요소가 된다.
8-1. ENUM 사용 시의 장단점
ENUM은 제한된 상태값을 저장할 수 있어 간단한 플래그 용도로는 유용하지만, 상태가 추가될 가능성이 있는 컬럼에는 적합하지 않다.
status ENUM('WAIT', 'DONE') -- 새로운 상태 'CANCEL' 추가 시 DDL 변경 필요
이런 경우는 TINYINT와 별도 상태 매핑 테이블을 사용하는 방식이 더 유연하다.
8-2. 고정 폭 타입 vs 가변 길이 타입
- CHAR(n)은 모든 값이 고정 길이
- VARCHAR(n)은 입력 길이에 따라 유동적 저장
회원 번호나 우편번호처럼 항상 정해진 길이로 입력되는 값은 CHAR을 쓰는 것이 효율적일 수 있다. 하지만 대부분의 텍스트는 VARCHAR가 더 적절하다.
8-3. 테이블 간의 관계 구조는 처음부터 설계할 것
초기 단계에서 단일 테이블에 모든 정보를 몰아넣는 구조로 시작했다가 이후 관계형 설계를 하려 하면 전체 마이그레이션이 필요하다.
- 사용자 정보, 권한, 활동 이력 등은 분리된 테이블로 설계
- 게시판, 댓글, 첨부파일은 외래 키 구조 기반으로 연결
확장성과 유지보수성을 높이기 위해 처음부터 제약 조건을 활용한 관계 구조 설계가 필요하다.
'컴퓨터공학' 카테고리의 다른 글
SQL 트랜잭션과 롤백 처리 완전 정복: 데이터 신뢰성을 지키는 핵심 기술 (0) | 2025.05.15 |
---|---|
SQL 인덱스와 성능 최적화 기본기: 데이터베이스를 빠르게 만드는 기술 (0) | 2025.05.15 |
SQL 정렬과 페이징 처리 완전 정복: 성능과 UX를 모두 고려한 설계 (0) | 2025.05.14 |
SQL 조건 처리 함수 완전 정복: CASE, IF, COALESCE, NULLIF (0) | 2025.05.14 |
SQL JOIN 완전 정복: 테이블을 연결하는 진짜 기술 (0) | 2025.04.03 |