데이터베이스: RDB를 실제로 설계하기 전 알아야 하는 기본 개념들에 대해서 알아봅시다.
2026.08.12. DB
- 데이터베이스
- RDB
- 정규화
- 스키마
지난 글에서 데이터베이스가 왜 필요한지, RDB와 NoSQL이 어떻게 다른지 살펴봤어요.
이번엔 그중에서도 RDB를 실제로 설계할 때 꼭 알아야 하는 기본 개념인 테이블/키, 테이블 간 관계, 그리고 정규화에 대해서 정리해볼게요.
테이블(Table), 스키마(Schema), 그리고 키(Key)
테이블과 스키마
- 테이블(Table): 관계형 DB에서 데이터를 저장하는 기본 단위예요. 행(row)과 열(column)로 이루어진 표입니다.
- 스키마(Schema): 테이블이 어떤 구조를 가지는지에 대한 “설계도”예요. 어떤 컬럼이 있고, 각 컬럼의 데이터 타입이 무엇이고, 어떤 제약조건(NOT NULL, UNIQUE 등)이 있는지를 정의해요.
PK(기본키)
PK(Primary Key)는 테이블에서 각 행을 유일하게 식별하는 컬럼(들)이에요. 두 가지 조건을 반드시 만족해야 해요.
- 유일성(Unique): 같은 값이 두 번 나올 수 없어요.
- NOT NULL: 비어있을 수 없어요.
FK(외래키)
FK(Foreign Key)는 다른 테이블의 PK를 참조하는 컬럼이에요. 테이블 간의 “관계”를 만드는 역할을 해요.
CREATE TABLE enrollments (
student_id INT REFERENCES students(student_id),
course_id INT REFERENCES courses(course_id),
PRIMARY KEY (student_id, course_id)
);
이렇게 FK를 걸어두면 enrollments 테이블에 존재하지 않는 student_id가 들어오는 걸 DB가 막아줘요. 이런 성질을 참조 무결성(referential integrity)이라고 해요.
테이블 간 관계
테이블을 나누고 나면, 이 테이블들이 서로 어떤 식으로 연결되는지가 중요해져요. 관계는 크게 세 가지예요.
1:1 관계
한 행이 다른 테이블의 정확히 한 행과만 연결돼요.
- 예:
user↔user_profile(한 유저는 프로필 하나만 가짐)
1:N 관계
한쪽의 행 하나가 다른 쪽 여러 행과 연결돼요.
-
예: 학생 한 명이 여러 수강 기록을 가짐 →
student(1) ↔enrollment(N) -
이때는 N 쪽에 FK를 넣어요. 위 예시라면
enrollment테이블에student_id라는 FK가 있는 식이죠.🤔 그런데 1:1 관계에서는 어느 쪽에 FK를 넣어야 할까요?
- 선택적인 데이터 쪽에 배치한다.
- 만약 user 테이블과 user_profile 테이블이 있다면, 처음 가입 시 프로필은 나중에 작성하기에 가입 시에는 필요하지 않을 수 있어요 !
- 만약 user 테이블에 profile_id 를 넣게 된다면 프로필이 없는 유저의 행에는
NULL이 들어가기에 공간 낭비가 됩니다. - 따라서 user_profile 에 user_id 를 넣는 것이 더 옳바른 선택이 될 수 있어요.
- 종속적인 데이터 쪽에 배치한다
- 만약 두 테이블 간에 명확하게 부모와 자식 관계처럼 종속적인 관계가 존재한다면, 자식 테이블에 FK 를 넣는 것이 일반적입니다.
- 직원 과 사원증 테이블이 존재한다면, 사원증에 직원id 를 FK 를 배치하는 것이 더 합당할 것 같아요.
- 왜냐하면, 사원증은 직원 없이는 존재할 수 없기 때문이죠 !
- 조회 빈도나 접근하는 쿼리문으로 고려할 수 있어요.
- 만약에 성능 최적화가 정말 중요한 경우에는 조회를 더 자주 하는 주 테이블에 FK 를 두기도 합니다.
- 이를 통해서 JOIN 연산 없이 참조 대상의 존재 여부나 식별자를 바로 확인할 수 있습니다.
M:N 관계
양쪽 다 여러 개와 연결될 수 있어요.
- 예: 학생 한 명이 여러 강좌를 듣고, 강좌 하나에도 여러 학생이 있음
- M:N은 FK 하나로는 표현이 안 돼요. 그래서 중간에 **연결 테이블(junction table) **을 하나 더 둬요. 방금 살펴본
enrollments가 바로 그 역할이에요.student_id와course_id를 각각 FK로 가지면서, 학생과 강좌를 다대다로 이어줍니다.
정규화: 왜 테이블을 쪼갤까
하나로 합쳐두면 생기는 문제
학생·강좌 정보를 아래처럼 테이블 하나에 다 몰아넣었다고 해볼게요.
| student_id | student_name | course_id | course_name | professor |
|------------|--------------|-----------|-------------|-----------|
| 1 | 김철수 | C1 | 알고리즘 | 박교수 |
| 2 | 이영희 | C1 | 알고리즘 | 박교수 |
| 1 | 김철수 | C2 | DB개론 | 최교수 |
편해 보이지만, 이렇게 두면 **세 가지 이상 현상(anomaly)**이 생겨요.
- 삽입 이상(Insertion Anomaly): 아직 수강생이 없는 새 강좌(C3, 자료구조, 박교수)를 등록하려면 어떡하죠?
student_id를 채울 방법이 없어요. - 갱신 이상(Update Anomaly): DB개론 담당 교수가 최교수에서 김교수로 바뀌면, C2를 듣는 모든 행의
professor값을 일일이 찾아 바꿔야 해요. 하나라도 빠뜨리면 데이터가 불일치해요. - 삭제 이상(Deletion Anomaly): 김철수가 DB개론 수강을 취소해서 그 행을 지우면, “최교수가 DB개론을 가르친다”는 정보 자체가 통째로 사라져요.
정규화(Normalization)는 이런 문제를 줄이려고 테이블을 여러 개로 쪼개는 설계 기법이에요.
- 핵심은 “하나의 테이블은 하나의 주제(entity)만 다뤄야 한다”는 거예요!
- 서로 다른 성격의 정보를 억지로 한 테이블에 몰아넣지 않는 거죠.
정규화 단계
1NF (제1정규형): 모든 컬럼 값이 원자값(더 쪼갤 수 없는 값)이어야 해요. 예를 들어 한 칸에 “국어, 영어, 수학”처럼 여러 값을 넣지 않는 거예요.
2NF (제2정규형): 1NF를 만족하면서, 기본키가 복합키(여러 컬럼으로 이루어진 키)일 때 그 일부에만 종속되는 컬럼(부분 함수 종속)이 없어야 해요.
enrollments_bad(student_id, course_id, student_name, course_name)
PK = (student_id, course_id)
여기서 student_name은 PK 전체가 아니라 student_id에만 종속되고, course_name은 course_id에만 종속돼요. 이게 부분 함수 종속이라 2NF 위반이에요. 그래서 앞서 본 것처럼 students, courses, enrollments 세 테이블로 쪼개는 거예요.
3NF (제3정규형): 2NF를 만족하면서, 기본키가 아닌 컬럼끼리 서로 종속하는 것(이행적 함수 종속)도 없어야 해요.
courses_bad(course_id, course_name, professor_id, professor_office)
PK = course_id
여기서 professor_office는 course_id가 아니라 professor_id에 종속돼요. course_id → professor_id → professor_office처럼 한 다리 건너 종속되는 거라 3NF 위반이에요. 이 경우엔 professors(professor_id, professor_office) 테이블을 따로 빼면 해결돼요.
1️⃣ 1NF: 모든 컬럼 값이 원자값, 한 칸에 여러 값 넣지 않기
2️⃣ 2NF: 1NF + 부분 함수 종속 제거, 복합키 중 일부에만 종속되는 컬럼 분리
3️⃣ 3NF: 2NF + 이행적 함수 종속 제거, 키가 아닌 컬럼끼리의 종속 관계 분리
트레이드오프: 무조건 쪼갠다고 좋은 게 아니에요
정규화를 많이 할수록 데이터 중복과 이상 현상은 줄어들지만, 테이블이 많아져서 조회할 때 JOIN이 늘어나고 그만큼 성능이 떨어질 수 있어요. 그래서 실무에서는 조회 성능이 중요한 곳엔 일부러 정규화를 되돌리는 반정규화(denormalization)를 하기도 해요. (이건 나중에 성능과 설계를 다루는 글에서 좀 더 파볼게요.)
정리하며
RDB는 테이블/스키마/PK·FK로 데이터를 구조화하고, FK로 테이블 간 1:1·1:N·M:N 관계를 표현해요. 그리고 테이블 하나에 여러 주제를 몰아넣으면 삽입·갱신·삭제 이상이 생기기 때문에, 정규화(1NF→2NF→3NF)로 테이블을 쪼개서 이를 줄여요. 다만 정규화가 늘어날수록 JOIN도 늘어나니, 상황에 따라 반정규화로 균형을 맞추기도 해요.
다음에는 기본적인 쿼리문에 대해서 살펴봅시다 !