TIL - 20260529

juni·2026년 5월 29일

TIL

목록 보기
365/468

0529 풀스택 실무 기초 (3/N): 데이터베이스와 ORM 기본 구조


✅ 1. 데이터베이스란 무엇인가?

  • 데이터베이스(Database, DB)는 서비스에서 사용하는 데이터를 체계적으로 저장하고 관리하는 공간입니다.
  • 웹서비스는 단순히 화면과 API만으로 동작하지 않습니다.
  • 회원 정보, 상품 정보, 주문 정보, 상담 신청 내역, 관리자 설정, 이벤트 데이터 등 대부분의 중요한 데이터는 DB에 저장됩니다.

➕ 1-1. 풀스택 개발에서 DB가 중요한 이유

  • 프론트엔드는 사용자가 데이터를 입력하고 확인하는 화면을 담당합니다.
  • 백엔드는 사용자의 요청을 받아 비즈니스 로직을 처리합니다.
  • DB는 그 결과를 저장하고, 다시 조회할 수 있게 해줍니다.
React 화면
  ↓
API 요청
  ↓
NestJS 서버
  ↓
Prisma / ORM
  ↓
Database
  • 예를 들어 사용자가 상담 신청을 하면 다음 흐름이 발생합니다.
1. 사용자가 상담 신청 폼 입력
2. React에서 POST /api/consults 요청
3. NestJS에서 요청 데이터 검증
4. Prisma를 통해 DB에 상담 신청 데이터 저장
5. 저장된 결과를 API 응답으로 반환
6. React에서 완료 메시지 표시

✅ 2. 관계형 데이터베이스

  • 관계형 데이터베이스(Relational Database)는 데이터를 표 형태의 테이블로 저장하는 DB입니다.
  • 실무에서 많이 사용하는 MySQL, PostgreSQL, MariaDB, Oracle 등이 관계형 DB에 해당합니다.

➕ 2-1. 테이블

  • 테이블(Table)은 같은 종류의 데이터를 저장하는 구조입니다.
  • 엑셀 시트처럼 행과 열로 구성되어 있습니다.
users 테이블

| id | name   | email             | createdAt           |
|---|--------|-------------------|---------------------|
| 1 | 홍길동 | test@example.com  | 2026-05-29 10:00:00 |
| 2 | 김철수 | kim@example.com   | 2026-05-29 11:00:00 |
  • 예를 들어 쇼핑몰이나 휴대폰 판매 사이트에서는 다음과 같은 테이블이 있을 수 있습니다.
테이블저장하는 데이터
users사용자 정보
admins관리자 계정
products상품 정보
orders주문 정보
consults상담 신청 내역
events이벤트 정보
banners배너 정보
logs운영 로그

✅ 3. 컬럼과 레코드

➕ 3-1. 컬럼

  • 컬럼(Column)은 테이블에서 데이터의 항목을 의미합니다.
  • 예를 들어 사용자 테이블에는 이름, 이메일, 전화번호 같은 컬럼이 있을 수 있습니다.
users 테이블의 컬럼 예시

id
name
email
phone
createdAt
updatedAt

➕ 3-2. 레코드

  • 레코드(Record)는 테이블에 저장된 실제 데이터 한 줄을 의미합니다.
  • Row, 행이라고도 부릅니다.
| id | name   | email            | phone        |
|---|--------|------------------|--------------|
| 1 | 홍길동 | test@example.com | 01012345678  |
  • 위 한 줄이 하나의 사용자 레코드입니다.

✅ 4. 기본키와 외래키

➕ 4-1. 기본키

  • 기본키(Primary Key)는 테이블의 각 레코드를 고유하게 식별하는 값입니다.
  • 보통 id 컬럼을 기본키로 사용합니다.
products 테이블

| id | name       | price   |
|---|------------|---------|
| 1 | Galaxy S25 | 1200000 |
| 2 | iPhone 16  | 1300000 |
  • 여기서 id는 각 상품을 구분하는 고유한 값입니다.
  • 기본키는 중복되면 안 되고, 비어 있어도 안 됩니다.

➕ 4-2. 외래키

  • 외래키(Foreign Key)는 다른 테이블의 기본키를 참조하는 컬럼입니다.
  • 테이블 간 관계를 연결할 때 사용합니다.
users 테이블

| id | name   |
|---|--------|
| 1 | 홍길동 |

orders 테이블

| id | userId | productName |
|---|--------|-------------|
| 1 | 1      | Galaxy S25  |
  • orders.userId는 users.id를 참조합니다.
  • 즉, 주문 데이터가 어떤 사용자와 연결되어 있는지 알 수 있습니다.

✅ 5. 테이블 관계

  • 관계형 DB에서는 테이블끼리 관계를 맺을 수 있습니다.
  • 실무에서 자주 사용하는 관계는 1:1, 1:N, N:M입니다.

➕ 5-1. 1:1 관계

  • 하나의 데이터가 다른 하나의 데이터와만 연결되는 관계입니다.
User 1명 ↔ Profile 1개
  • 예시:

    • 사용자와 사용자 상세 프로필
    • 관리자 계정과 관리자 설정

➕ 5-2. 1:N 관계

  • 하나의 데이터가 여러 개의 데이터와 연결되는 관계입니다.
  • 실무에서 가장 자주 사용됩니다.
User 1명 → Order 여러 개
Event 1개 → Banner 여러 개
Product 1개 → Review 여러 개
  • 예시:

    • 한 명의 사용자가 여러 주문을 할 수 있음
    • 하나의 이벤트에 여러 배너가 연결될 수 있음
    • 하나의 상담 신청에 여러 처리 이력이 생길 수 있음

➕ 5-3. N:M 관계

  • 여러 데이터가 서로 여러 개씩 연결되는 관계입니다.
  • 보통 중간 테이블을 만들어서 관리합니다.
Product 여러 개 ↔ Category 여러 개
User 여러 명 ↔ Role 여러 개
  • 예를 들어 하나의 상품이 여러 카테고리에 속할 수 있고, 하나의 카테고리에 여러 상품이 들어갈 수 있습니다.
products
categories
product_categories
  • product_categories 같은 중간 테이블을 통해 상품과 카테고리 관계를 관리합니다.

✅ 6. SQL 기본 개념

  • SQL(Structured Query Language)은 관계형 DB에 명령을 내리는 언어입니다.
  • 데이터를 조회, 생성, 수정, 삭제할 때 사용합니다.

➕ 6-1. SELECT

  • 데이터를 조회할 때 사용합니다.
SELECT * FROM users;
  • 특정 컬럼만 조회할 수도 있습니다.
SELECT id, name, email FROM users;

➕ 6-2. INSERT

  • 새 데이터를 추가할 때 사용합니다.
INSERT INTO users (name, email, phone)
VALUES ('홍길동', 'test@example.com', '01012345678');

➕ 6-3. UPDATE

  • 기존 데이터를 수정할 때 사용합니다.
UPDATE users
SET phone = '01099998888'
WHERE id = 1;
  • 주의할 점

    • WHERE 조건 없이 UPDATE를 실행하면 모든 데이터가 수정될 수 있습니다.
    • 운영 DB에서 직접 UPDATE를 실행할 때는 매우 조심해야 합니다.

➕ 6-4. DELETE

  • 데이터를 삭제할 때 사용합니다.
DELETE FROM users
WHERE id = 1;
  • 주의할 점

    • WHERE 조건 없이 DELETE를 실행하면 전체 데이터가 삭제될 수 있습니다.
    • 실무에서는 실제 삭제보다 deletedAt 컬럼을 사용하는 소프트 삭제를 많이 사용합니다.

✅ 7. WHERE, ORDER BY, LIMIT

➕ 7-1. WHERE

  • 특정 조건에 맞는 데이터만 조회할 때 사용합니다.
SELECT * FROM consults
WHERE status = 'PENDING';
  • 예를 들어 처리 대기 중인 상담 신청만 조회할 수 있습니다.

➕ 7-2. ORDER BY

  • 조회 결과를 정렬할 때 사용합니다.
SELECT * FROM consults
ORDER BY createdAt DESC;
  • DESC는 내림차순, ASC는 오름차순입니다.
  • 최신 상담 신청을 먼저 보여줄 때는 보통 createdAt DESC를 사용합니다.

➕ 7-3. LIMIT

  • 조회할 데이터 개수를 제한할 때 사용합니다.
SELECT * FROM consults
ORDER BY createdAt DESC
LIMIT 20;
  • 관리자 페이지 목록 조회나 페이지네이션에서 자주 사용합니다.

✅ 8. JOIN 기본 개념

  • JOIN은 여러 테이블에 나누어 저장된 데이터를 함께 조회할 때 사용합니다.
  • 관계형 DB에서 매우 중요한 개념입니다.

➕ 8-1. JOIN이 필요한 이유

  • 데이터를 하나의 큰 테이블에 모두 넣으면 중복이 많아지고 관리가 어려워집니다.
  • 그래서 사용자, 주문, 상품처럼 성격이 다른 데이터는 각각 다른 테이블에 저장하고, 필요할 때 JOIN으로 연결합니다.
orders 테이블에는 userId와 productId만 저장
users 테이블에는 사용자 정보 저장
products 테이블에는 상품 정보 저장

주문 목록 화면에서는 JOIN으로 사용자 이름과 상품명을 함께 조회

➕ 8-2. JOIN 예시

SELECT
  orders.id,
  users.name AS userName,
  products.name AS productName,
  orders.createdAt
FROM orders
JOIN users ON orders.userId = users.id
JOIN products ON orders.productId = products.id;
  • 위 쿼리는 주문 정보와 사용자 이름, 상품명을 함께 조회합니다.

✅ 9. 인덱스 기본 개념

  • 인덱스(Index)는 DB 조회 속도를 빠르게 하기 위한 자료구조입니다.
  • 책의 목차나 색인처럼, 원하는 데이터를 더 빨리 찾을 수 있게 도와줍니다.

➕ 9-1. 인덱스가 필요한 경우

  • 자주 검색하는 컬럼
  • 자주 정렬하는 컬럼
  • JOIN에 자주 사용되는 외래키 컬럼
  • WHERE 조건에 자주 들어가는 컬럼
CREATE INDEX idx_consults_phone ON consults(phone);
CREATE INDEX idx_consults_createdAt ON consults(createdAt);
  • 상담 신청 목록에서 전화번호 검색을 자주 한다면 phone 컬럼에 인덱스를 고려할 수 있습니다.
  • 최신순 정렬을 자주 한다면 createdAt 컬럼도 인덱스 후보가 될 수 있습니다.

➕ 9-2. 인덱스 주의점

  • 인덱스가 많다고 무조건 좋은 것은 아닙니다.
  • 조회는 빨라질 수 있지만, INSERT, UPDATE, DELETE 성능은 느려질 수 있습니다.
  • 사용하지 않는 인덱스가 많으면 DB 용량도 늘어납니다.
  • 실무에서는 느린 쿼리를 확인한 뒤 필요한 컬럼에만 인덱스를 추가하는 것이 좋습니다.

✅ 10. ORM이란 무엇인가?

  • ORM(Object Relational Mapping)은 객체 지향 코드와 관계형 DB를 연결해주는 도구입니다.
  • SQL을 직접 많이 작성하지 않아도 TypeScript나 JavaScript 코드로 DB를 다룰 수 있게 해줍니다.

➕ 10-1. ORM을 사용하는 이유

  • SQL을 직접 작성하는 양을 줄일 수 있습니다.
  • 타입 안정성을 얻을 수 있습니다.
  • 테이블 구조를 코드로 관리할 수 있습니다.
  • CRUD 작업을 더 일관되게 작성할 수 있습니다.
  • 마이그레이션을 통해 DB 구조 변경을 관리할 수 있습니다.

➕ 10-2. 대표적인 ORM

언어/환경ORM
Node.js / TypeScriptPrisma, TypeORM, Sequelize
Java / KotlinJPA, Hibernate
PythonSQLAlchemy, Django ORM
PHPEloquent
  • NestJS와 TypeScript 환경에서는 Prisma 또는 TypeORM을 많이 사용합니다.
  • 최근 실무에서는 타입 안정성과 개발 경험 때문에 Prisma를 사용하는 경우가 많습니다.

✅ 11. Prisma 기본 구조

  • Prisma는 Node.js/TypeScript 환경에서 사용하는 ORM입니다.
  • schema.prisma 파일에 데이터 모델을 정의하고, Prisma Client를 통해 DB에 접근합니다.

➕ 11-1. Prisma 모델 예시

model Consult {
  id        Int      @id @default(autoincrement())
  name      String
  phone     String
  model     String
  status    String   @default("PENDING")
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
  • 위 모델은 상담 신청 데이터를 저장하는 Consult 테이블 구조를 의미합니다.
필드의미
id기본키
name신청자 이름
phone전화번호
model관심 모델
status상담 상태
createdAt생성일
updatedAt수정일

➕ 11-2. Prisma Client 사용 예시

const consult = await prisma.consult.create({
  data: {
    name: '홍길동',
    phone: '01012345678',
    model: 'Galaxy S25',
  },
});
  • 위 코드는 SQL의 INSERT와 비슷한 역할을 합니다.
const consults = await prisma.consult.findMany({
  where: {
    status: 'PENDING',
  },
  orderBy: {
    createdAt: 'desc',
  },
  take: 20,
});
  • 위 코드는 처리 대기 중인 상담 신청을 최신순으로 20개 조회합니다.

✅ 12. 마이그레이션

  • 마이그레이션(Migration)은 DB 테이블 구조 변경 이력을 관리하는 작업입니다.
  • 예를 들어 상담 신청 테이블에 memo 컬럼을 추가하거나, 상품 테이블에 isVisible 컬럼을 추가하는 것이 마이그레이션입니다.

➕ 12-1. 마이그레이션이 필요한 이유

  • DB 구조 변경을 기록으로 남길 수 있습니다.
  • 개발 환경, 테스트 환경, 운영 환경의 DB 구조를 맞출 수 있습니다.
  • 팀원 간 DB 구조 차이를 줄일 수 있습니다.
  • 배포 시 어떤 DB 변경이 필요한지 추적할 수 있습니다.

➕ 12-2. Prisma 마이그레이션 예시

npx prisma migrate dev --name add_consult_memo
  • 개발 환경에서 마이그레이션 파일을 생성하고 DB에 적용합니다.
npx prisma migrate deploy
  • 운영 환경에서는 이미 생성된 마이그레이션 파일을 적용할 때 사용합니다.

➕ 12-3. 주의할 점

  • 운영 DB에서 컬럼 삭제, 타입 변경, 대량 데이터 수정은 매우 조심해야 합니다.
  • 마이그레이션 전에는 반드시 백업을 확인해야 합니다.
  • 운영 배포 전 로컬 또는 테스트 DB에서 먼저 검증해야 합니다.

✅ 13. Prisma와 SQL의 관계

  • ORM을 사용한다고 해서 SQL을 몰라도 되는 것은 아닙니다.
  • ORM은 SQL을 편하게 작성하게 도와주는 도구일 뿐, 실제로는 DB에 SQL이 실행됩니다.
await prisma.consult.findMany({
  where: {
    status: 'PENDING',
  },
});
  • 위 Prisma 코드는 내부적으로 다음과 비슷한 SQL로 변환됩니다.
SELECT * FROM Consult
WHERE status = 'PENDING';
  • 실무에서 문제가 생겼을 때는 ORM 코드만 보는 것이 아니라, 실제 실행되는 쿼리와 DB 상태를 함께 확인해야 합니다.

✅ 14. 실무에서 자주 생기는 DB 문제

➕ 14-1. DB 연결 실패

  • 백엔드 서버가 DB에 연결하지 못하는 문제입니다.

  • 가능한 원인

    • DB 서버가 꺼져 있음
    • DB 주소 또는 포트 오류
    • 계정/비밀번호 오류
    • 방화벽 또는 보안 그룹 문제
    • Docker 컨테이너 미실행
    • .env의 DATABASE_URL 오류
Can't reach database server at localhost:5432
  • 이런 오류가 발생하면 먼저 DB가 실제로 실행 중인지, 포트가 맞는지, 환경변수가 맞는지 확인해야 합니다.

➕ 14-2. 느린 쿼리

  • 데이터가 많아질수록 조회가 느려질 수 있습니다.

  • 가능한 원인

    • 인덱스 없음
    • 너무 많은 데이터를 한 번에 조회
    • 불필요한 JOIN
    • 비효율적인 검색 조건
    • 페이지네이션 미적용

➕ 14-3. 데이터 중복

  • 같은 전화번호, 같은 이메일, 같은 주문 번호가 중복 저장되는 문제입니다.

  • 대응 방법

    • DB에 unique 제약조건 추가
    • 서버에서 중복 검증
    • 프론트엔드에서 중복 클릭 방지
    • 요청 처리 중복 방지 로직 적용
model User {
  id    Int    @id @default(autoincrement())
  email String @unique
}

➕ 14-4. 잘못된 삭제

  • 운영 DB에서 실수로 데이터를 삭제하는 문제입니다.

  • 실제 실무에서 매우 위험한 사고입니다.

  • 대응 방법

    • 운영 DB 직접 접근 최소화
    • 삭제 전 백업 확인
    • DELETE보다 소프트 삭제 사용
    • 관리자 삭제 기능에 확인 절차 추가
    • 삭제 로그 기록
model Consult {
  id        Int       @id @default(autoincrement())
  name      String
  phone     String
  deletedAt DateTime?
}

✅ 15. 실무 체크리스트

➕ 15-1. 테이블 설계 체크리스트

  1. 이 데이터는 어떤 테이블에 저장해야 하는가?
  2. 기본키가 있는가?
  3. 필수값과 선택값이 구분되어 있는가?
  4. 중복되면 안 되는 컬럼에 unique 제약조건이 있는가?
  5. 다른 테이블과 관계가 필요한가?
  6. 자주 검색하는 컬럼에 인덱스가 필요한가?
  7. 생성일과 수정일 컬럼이 있는가?
  8. 삭제가 필요한 데이터라면 소프트 삭제를 고려했는가?
  9. 개인정보가 포함되는가?
  10. 백업과 복구가 필요한 중요한 데이터인가?

➕ 15-2. Prisma 사용 체크리스트

  1. schema.prisma 모델이 실제 요구사항과 맞는가?
  2. 마이그레이션 파일을 생성했는가?
  3. 운영 배포 전 테스트 DB에서 검증했는가?
  4. DATABASE_URL이 환경별로 올바른가?
  5. Prisma Client를 재생성했는가?
  6. 관계 설정이 올바른가?
  7. unique, index, relation 설정이 필요한 곳에 적용되었는가?
  8. 대량 조회 API에 take, skip 같은 제한이 있는가?
  9. 삭제 로직이 안전한가?
  10. 에러 발생 시 서버 로그에 원인을 남기는가?

📌 요약

  • 데이터베이스는 서비스의 핵심 데이터를 저장하고 관리하는 공간입니다.
  • 관계형 DB는 데이터를 테이블, 컬럼, 레코드 형태로 저장하며, 기본키와 외래키를 통해 데이터 관계를 표현합니다.
  • SQL은 데이터를 조회, 생성, 수정, 삭제하기 위한 언어이며, SELECT, INSERT, UPDATE, DELETE는 반드시 알아야 합니다.
  • JOIN은 여러 테이블의 데이터를 함께 조회할 때 사용하고, 인덱스는 조회 성능을 개선하는 데 사용합니다.
  • ORM은 코드로 DB를 다룰 수 있게 해주는 도구이며, Prisma는 TypeScript/NestJS 환경에서 자주 사용됩니다.
  • ORM을 사용해도 SQL과 DB 기본 개념은 반드시 알아야 합니다.
  • 마이그레이션은 DB 구조 변경 이력을 관리하는 작업이며, 운영 배포 전 백업과 테스트가 중요합니다.
  • 실무에서는 DB 연결 실패, 느린 쿼리, 데이터 중복, 잘못된 삭제 같은 문제가 자주 발생하므로 기본 체크리스트를 갖추는 것이 중요합니다.

0개의 댓글