관계형 DB 기초
관계형 데이터베이스(RDBMS)는 데이터를 표(테이블) 형태로 저장합니다. 엑셀 스프레드시트를 떠올리면 됩니다 — 세로줄(컬럼)마다 어떤 종류의 정보를 담을지 정해두고, 가로줄(행)마다 실제 데이터 한 건씩을 담습니다. 여기에 기본키(PK)로 각 행을 구별하고, 외래키(FK)로 서로 다른 테이블을 연결하면 관계형 모델의 뼈대가 완성됩니다.
핵심 개념
테이블(Table)
하나의 주제(예: 사용자, 주문)에 대한 데이터를 모아두는 표입니다. 컬럼(열)과 레코드(행)로 이루어져 있습니다.
핵심 개념
컬럼(Column)
테이블의 세로줄이며, 하나의 속성(attribute)을 의미합니다. id, name, email처럼 어떤 값을 저장할지 미리 정해집니다.
핵심 개념
레코드(Row)
테이블의 가로줄이며, 실제 데이터 한 건입니다. 모든 컬럼에 대응하는 값을 하나씩 가지고 있습니다.
핵심 개념
기본키(Primary Key)
각 레코드를 유일하게 식별하는 컬럼입니다. 값이 중복될 수 없고(UNIQUE), 비어있을 수도 없습니다(NOT NULL).
핵심 개념
외래키(Foreign Key)
다른 테이블의 기본키를 그대로 저장해 두 테이블을 연결하는 컬럼입니다. 존재하지 않는 값은 참조할 수 없습니다.
핵심 개념
관계(Relationship)
외래키 덕분에 한 사용자가 여러 주문을 가지는 것처럼(1:N), 서로 다른 테이블의 데이터를 자연스럽게 이어 볼 수 있습니다.
관계형 모델이 나온 배경
관계형 모델은 에드거 코드(E. F. Codd)가 1970년 논문 "A Relational Model of Data for Large Shared Data Banks"에서 제안했습니다. 데이터를 값의 집합으로 다루고 저장 방식과 분리해서 보게 하자는 생각이 바탕입니다. 교재에서는 테이블을 릴레이션(relation), 행을 튜플(tuple), 컬럼을 어트리뷰트(attribute)라고 부릅니다. 이 토픽의 테이블·행·컬럼은 같은 개념을 실무에서 쓰는 이름으로 부른 것입니다.
엑셀과 다른 점
키의 종류
자주 하는 오해
= NULL이 아니라 IS NULL로 확인합니다.
면접에서 이렇게 물어요
면접 질문
기본키와 UNIQUE 제약은 무엇이 다른가요?
둘 다 값의 중복을 막습니다. 기본키는 테이블에 하나만 둘 수 있고 NULL이 안 됩니다. UNIQUE는 테이블에 여러 개 둘 수 있고, PostgreSQL에서는 NULL을 여러 개 넣을 수 있습니다(제품마다 다릅니다).
면접 질문
외래키를 거는 이유와 걸지 않는 경우를 말해 보세요.
존재하지 않는 부모 행을 가리키는 자식 행이 저장되는 것을 DB가 막아 주므로 정합성을 지킬 수 있습니다. 대신 입력과 삭제 때 DB가 참조를 확인하는 비용이 듭니다.
면접 질문
1:N과 N:M 관계는 어떻게 표현하나요?
1:N은 N쪽 테이블에 1쪽의 기본키를 외래키로 둡니다. N:M은 양쪽 기본키를 외래키로 가지는 연결 테이블(교차 테이블)을 따로 만들어 두 개의 1:N으로 바꿉니다.
면접 질문
외래키가 없어도 JOIN을 할 수 있나요?
할 수 있습니다. JOIN은 조건식으로 행을 잇는 연산이어서 외래키가 선언되어 있는지와 상관없이 쓸 수 있습니다. 외래키는 그 연결이 깨지지 않도록 지켜 주는 제약일 뿐입니다.
실제 시스템에서는
\d 테이블이름을 실행하면 컬럼, 기본키, 외래키를 볼 수 있습니다.
함께 보면 좋은 토픽
참고 자료
SQL — 기본키(PK) / 외래키(FK) 정의
-- USERS: id를 기본키로 지정 (자동 증가) CREATE TABLE users ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL ); -- ORDERS: user_id를 외래키로 지정 (USERS.id를 참조) CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, product VARCHAR(50) NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) );
PRIMARY KEY는 id가 유일하고(UNIQUE) 비어있지 않음(NOT NULL)을 DB 차원에서 보장합니다.
FOREIGN KEY ... REFERENCES는 orders.user_id에 users.id에 없는 값을 넣으려는 시도를 DB가 직접 거부하게 만듭니다 — 시각화의 "외래키 관계" 탭에서 본 거부 동작이 바로 이 제약 조건입니다.
Java — JPA Entity로 기본키 / 외래키 표현
@Entity @Table(name = "users") @Getter public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // 기본키 — DB가 자동 채번 private Long id; @Column(nullable = false) private String name; @Column(nullable = false) private String email; } @Entity @Table(name = "orders") @Getter public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) // 외래키 — USERS.id 참조 private User user; @Column(nullable = false) private String product; }
User.id는 @Id + @GeneratedValue로 기본키를, Order.user는 @JoinColumn(name = "user_id")로 외래키를 표현합니다.
객체지향 코드에서는 숫자 id 대신 User user 필드를 직접 들고 있는 것처럼 보이지만, 실제 테이블에는 SQL 예시와 동일하게 user_id 컬럼(외래키 값)만 저장됩니다.
Java — 기본키로 조회하기 (Repository / Service)
public interface OrderRepository extends JpaRepository<Order, Long> { // LEFT JOIN FETCH: 연관된 User를 한 번에 가져와 LazyInitializationException 방지 @Query("SELECT o FROM Order o LEFT JOIN FETCH o.user WHERE o.id = :id") Optional<Order> findByIdWithUser(@Param("id") Long id); } @Service @RequiredArgsConstructor @Transactional public class OrderService { private final OrderRepository orderRepository; public Order getOrder(Long id) { return orderRepository.findByIdWithUser(id) .orElseThrow(() -> new NotFoundException("주문을 찾을 수 없습니다: id=" + id)); } }
기본키(id)로 조회할 땐 결과가 없을 수도 있으므로 Optional로 감싸고, Optional.get() 대신 orElseThrow()로 없을 경우를 명시적으로 처리합니다.
LEFT JOIN FETCH로 외래키가 가리키는 User까지 한 번의 쿼리로 함께 가져오는 것이 이 프로젝트의 표준 패턴입니다.
Q1
관계형 데이터베이스에서 기본키(Primary Key)의 역할은?
Q2
외래키(Foreign Key)의 역할은?
Q3
테이블, 컬럼, 레코드(행)의 관계로 옳은 것은?