PH pullh
입문 블로그 / 데이터베이스는 왜 필요한가를 먼저 이해하자
웹과 데이터 5분 Beginner

데이터베이스는 왜 필요한가를 먼저 이해하자

SQL 문법을 외우기 전에, 왜 파일만으로는 부족해지고 데이터베이스가 필요한지부터 감각을 잡아 보자.

DB 감각 커버 이미지

처음에는 변수와 파일만 있어도 웬만한 예제를 만들 수 있습니다. 그런데 사용자가 늘고, 데이터를 다시 찾아야 하고, 수정과 삭제가 필요해지면 금방 한계가 보입니다.

데이터베이스는 복잡한 기술이기 전에, 프로그램이 기억을 오래 유지하고 정리해서 찾게 해 주는 시스템입니다. SQL 문법을 외우기 전에 이 쓰임새부터 잡아 두면, 나중에 배우는 문장 하나하나가 훨씬 덜 낯설게 느껴집니다.

파일 한 장으로 버틸 수 있는 구간

메모장 파일이나 JSON 파일에 회원 정보를 줄줄이 적어 두는 방식은 생각보다 오래 버팁니다. 데이터가 수십 건이고, 프로그램을 쓰는 사람이 나 하나이고, 매번 전체를 읽어서 처리해도 눈 깜짝할 사이에 끝난다면 파일로 충분합니다. 이 단계에서 굳이 데이터베이스를 얹으면 배울 것만 늘고 얻는 것은 적습니다.

문제는 그다음입니다. 파일 저장은 서서히 나빠지지 않고, 어느 지점에서 갑자기 무너집니다. 그리고 무너지는 방식이 늘 비슷합니다. 그 지점을 미리 알아 두면 “아, 지금이 데이터베이스를 꺼낼 때구나”를 스스로 판단할 수 있습니다.

파일 저장이 무너지는 네 가지 순간

첫째, 두 군데에서 동시에 쓰는 순간입니다. 웹 서버가 요청을 두 개 동시에 처리하면서 같은 파일을 각자 읽고 각자 통째로 다시 쓰면, 나중에 저장한 쪽이 앞선 쪽의 변경을 통째로 덮어씁니다. 코드에는 아무 오류도 나지 않고, 데이터만 조용히 사라집니다.

둘째, 조건을 걸어 찾는 순간입니다. “가입한 지 한 달이 안 된 사용자”를 뽑으려면 파일 전체를 읽어 하나씩 비교해야 합니다. 건수가 적을 때는 티가 안 나지만, 이런 질문이 화면마다 하나씩 늘어나면 전체를 읽는 일이 계속 반복됩니다.

셋째, 형식이 조금씩 어긋난 레코드가 쌓이는 순간입니다. 어제까지는 전화번호가 없어도 그냥 넘어갔는데 오늘부터 필수가 되었다면, 파일 안에는 전화번호가 있는 줄과 없는 줄이 섞입니다. 날짜를 어떤 줄은 2026-08-01로, 어떤 줄은 2026/8/1로 적어 둔 것도 흔합니다. 읽는 코드에는 예외 처리가 계속 붙습니다.

넷째, 쓰는 도중에 프로그램이 죽는 순간입니다. 파일을 새로 열어 절반쯤 쓰다가 종료되면 반만 써진 파일이 남습니다. 원본은 이미 지워졌고 새 파일은 완성되지 않았으니, 복구할 것이 없습니다.

데이터베이스가 해결하는 것이 정확히 이 네 가지입니다. 동시 쓰기는 트랜잭션과 잠금으로, 조건 검색은 인덱스로, 형식은 스키마와 제약으로, 중간에 죽는 문제는 커밋 단위로 막습니다.

머릿속 구조를 표로 적어 보기

데이터베이스 공부를 SELECT부터 시작하면 감이 잘 안 옵니다. 먼저 내가 다루는 데이터를 표 모양으로 적어 보는 편이 빠릅니다. 게시판을 예로 들면 사람, 글, 댓글 세 가지가 있고, 각각이 표 하나가 됩니다.

SCHEMA

CREATE TABLE users (
  id            INTEGER PRIMARY KEY,
  email         TEXT    NOT NULL UNIQUE,
  display_name  TEXT    NOT NULL,
  created_at    TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE posts (
  id          INTEGER PRIMARY KEY,
  author_id   INTEGER NOT NULL REFERENCES users(id),
  title       TEXT    NOT NULL,
  body        TEXT    NOT NULL,
  published   INTEGER NOT NULL DEFAULT 0,
  created_at  TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE comments (
  id          INTEGER PRIMARY KEY,
  post_id     INTEGER NOT NULL REFERENCES posts(id),
  author_id   INTEGER NOT NULL REFERENCES users(id),
  body        TEXT    NOT NULL,
  created_at  TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_comments_post ON comments(post_id);

SQLite에서 그대로 돌아가고, PostgreSQL에서는 INTEGER PRIMARY KEY를 SERIAL이나 GENERATED ALWAYS AS IDENTITY로 바꾸면 됩니다.

이 스무 줄 남짓에 앞에서 말한 규칙이 거의 다 들어 있습니다. PRIMARY KEY는 이 표에서 한 줄을 가리키는 유일한 이름입니다. UNIQUE는 같은 이메일로 두 번 가입할 수 없다는 뜻이고, NOT NULL은 빈칸을 허용하지 않겠다는 선언입니다. REFERENCES users(id)는 존재하지 않는 사용자가 쓴 글이 생길 수 없다는 뜻입니다.

중요한 것은 이 규칙들이 내 코드 밖에 적혀 있다는 점입니다. 파일 방식에서는 “이메일 중복 확인” 코드를 빼먹으면 그대로 중복이 들어갑니다. 스키마에 적어 두면, 어느 코드가 실수해도 데이터베이스가 거부합니다.

질문을 한 문장으로 던진다

표를 만들었으면 이제 질문할 수 있습니다. “공개된 글 중에서 댓글이 달린 순서대로, 누가 어떤 글에 무슨 댓글을 최근에 남겼는지 스무 개만 보여 달라”는 질문을 생각해 봅시다.

QUERY

SELECT u.display_name,
       p.title,
       c.body,
       c.created_at
FROM comments AS c
JOIN posts AS p ON p.id = c.post_id
JOIN users AS u ON u.id = c.author_id
WHERE p.published = 1
ORDER BY c.created_at DESC
LIMIT 20;

-- 결과 예시
-- display_name | title            | body              | created_at
-- 김선영        | 첫 배포 후기      | 저도 같은 데서 막혔어요 | 2026-08-29 21:14:02
-- 박도현        | SQLite 시작하기   | 인덱스 부분 감사합니다  | 2026-08-29 20:51:37
-- 김선영        | 첫 배포 후기      | 환경 변수는 어디에 두나요 | 2026-08-28 09:02:11

세 개의 표에 흩어져 있던 값을 한 줄로 합쳐서 돌려줍니다.

같은 일을 파일로 하려면 무슨 일이 벌어지는지 생각해 보면 차이가 분명해집니다. 댓글 파일을 전부 읽고, 각 댓글의 post_id로 글 파일을 다시 뒤져서 제목을 찾고, 그 글이 공개 상태인지 확인하고, 다시 사용자 파일을 뒤져 이름을 찾고, 마지막으로 전체를 시간순으로 정렬한 다음 앞의 스무 개만 남깁니다. 이 과정을 직접 짜면 수십 줄이 되고, 조건이 하나 바뀔 때마다 그 수십 줄을 다시 손봐야 합니다.

SQL이 좋은 이유는 짧아서가 아니라, 무엇을 원하는지만 적고 어떻게 찾을지는 맡긴다는 점 때문입니다. 인덱스를 쓸지, 어느 표부터 훑을지는 데이터베이스가 정합니다.

같은 사실을 두 군데에 적지 않는다

정규화라는 말이 어렵게 들리지만, 실무에서 쓰이는 감각은 이 한 줄로 요약됩니다. 위 스키마에서 댓글 표에는 작성자의 이름 대신 author_id만 들어 있습니다. 이름을 댓글마다 복사해 두면, 그 사람이 닉네임을 바꿨을 때 어느 줄은 새 이름이고 어느 줄은 옛 이름인 상태가 됩니다. 사실이 한 군데에만 적혀 있으면 고칠 곳도 한 군데입니다. 처음에는 이 원칙 하나만 지켜도 충분하고, 정규형 1차·2차·3차 같은 용어는 나중에 이름표로 붙여 배우면 됩니다.

데이터베이스가 답이 아닌 경우

여기까지 읽고 모든 저장을 데이터베이스로 옮기려 한다면, 그것도 같은 크기의 실수입니다. 파일이 더 나은 자리가 분명히 있습니다.

설정값 몇 줄은 파일이 낫습니다. API 주소나 포트 번호를 표에 넣으면 값을 확인하려고 연결부터 해야 합니다. 텍스트 파일이면 열어서 읽고 고치면 끝입니다. 한 번 돌리고 버릴 스크립트도 마찬가지입니다. CSV를 읽어 계산하고 결과를 출력하는 작업에 스키마를 설계하는 것은 낭비입니다.

로그도 파일 쪽이 자연스럽습니다. 로그는 끝에 계속 덧붙이기만 하고 중간을 고치지 않으므로, 파일에 append하는 방식이 가장 단순하고 프로그램이 죽어도 앞부분은 그대로 남습니다. 캐시처럼 없어져도 다시 만들면 그만인 데이터 역시 굳이 영구 저장소에 둘 이유가 없습니다.

학습 순서에도 함정이 있습니다. 요즘은 ORM으로 데이터베이스를 처음 만나기 쉬운데, user.posts 한 줄이 실제로는 반복문 안에서 쿼리를 수백 번 날리고 있을 수 있습니다. 어떤 SQL이 나가는지 모르면 왜 느린지도 알 수 없고, 고칠 방법도 떠오르지 않습니다. ORM을 쓰더라도 로그로 실제 쿼리를 한 번은 찍어 보는 습관을 권합니다.

정규화를 과하게 밀어붙이는 경우도 있습니다. 주소를 시·구·동 표로 쪼개고 그 표를 다시 쪼개면, 화면 하나를 그리려고 여섯 개의 표를 조인해야 합니다. 쿼리는 읽기 어려워지고 속도는 떨어집니다. 같이 바뀌지 않고 따로 조회할 일도 없는 값이라면 한 표에 두어도 괜찮습니다.

자주 하는 실수

표를 먼저 만들고 나중에 고치면 된다고 생각해 NOT NULL과 UNIQUE를 다 빼 두는 경우가 많습니다. 데이터가 쌓인 뒤에 제약을 추가하면 이미 규칙을 어긴 줄들 때문에 추가 자체가 거부됩니다. 제약은 처음에 붙이는 편이 훨씬 쌉니다.

오늘 30분 안에 해 볼 것

  • 지금 만들고 있는 프로그램의 데이터를 종이에 표 두세 개로 그려 보고, 각 표의 유일한 열이 무엇인지 정합니다.
  • 추가 설치 없이 쓸 수 있는 SQLite로 위 CREATE TABLE을 그대로 실행해 봅니다.
  • 같은 이메일로 INSERT를 두 번 넣어 보고, 거부되는 오류 메시지를 직접 읽어 봅니다.
  • 표에 담긴 데이터에 대해 궁금한 질문 세 개를 한국어로 적고, 그중 하나를 SELECT로 옮겨 봅니다.
  • 지금 파일로 저장 중인 값 가운데 설정·로그·캐시에 해당하는 것은 그대로 두기로 표시합니다.
알아두면 좋은 점

연습용 데이터베이스는 SQLite 하나면 충분합니다. 서버를 띄울 필요도, 계정을 만들 필요도 없이 파일 하나가 데이터베이스가 되고, 여기서 익힌 SQL은 PostgreSQL이나 MySQL에서도 거의 그대로 통합니다. 제품을 고르는 고민은 실제로 여러 명이 동시에 쓰는 상황이 왔을 때 해도 늦지 않습니다.

이어서 보면 좋은 글

데이터베이스는 대개 혼자 오지 않습니다. 화면에서 서버로 요청이 오가는 흐름을 함께 보면 표가 왜 그 모양이어야 하는지 이해가 빨라지므로 API 요청과 응답 이해하기를 같이 읽어 보시고, 사용자 데이터를 다루기 시작했다면 첫 프로젝트의 보안 기본기에서 비밀번호와 입력값 처리를 확인해 두시면 좋습니다. 파일 저장 쪽을 아직 손에 익히는 중이라면 파이썬 파일 입출력 가이드가 앞의 네 가지 증상을 직접 재현해 보기에 알맞습니다.

처음에는 SQL 문장보다 왜 저장하고, 왜 다시 찾고, 왜 정리해야 하는가를 이해하는 편이 오래갑니다.

웹과 데이터

Next Read

배포를 한 번 해 보면 코드가 다르게 보인다

로컬에서만 돌아가던 코드를 밖에 올려 보면, 파일 경로와 환경 변수, 실행 방식에 대한 감각이 달라진다.