본문 바로가기

데이터베이스 기초 (SQL vs NoSQL)

서버 메모리의 한계를 확인하고 관계형 SQL과 문서형 NoSQL의 구조·질의·확장 특성에 따라 저장소를 선택합니다.

앞 글 RESTful API 설계와 구현에서는 Express로 사용자 API를 만들었습니다. 하지만 그 API는 사용자 데이터를 서버 메모리의 let users = [...] 배열에 임시로 저장했기 때문에, 서버를 다시 시작하면 데이터가 모두 사라졌습니다. 실제 웹 애플리케이션은 사용자 정보, 게시물, 상품, 주문 내역 같은 많은 데이터를 오래 보관해야 하고, 여러 사용자가 동시에 데이터를 읽고 바꾸는 상황도 안정적으로 처리해야 합니다.

이런 요구를 채워 주는 것이 데이터베이스(database)입니다. 데이터베이스는 데이터를 체계적으로 저장하고 관리하는 백엔드의 핵심 구성 요소입니다. 이 글에서는 데이터베이스의 기본 개념과 종류, 웹 애플리케이션에서 데이터베이스를 쓰는 방식, 그리고 SQL의 기초 문법을 정리합니다.


데이터베이스란

데이터베이스는 특정 목적에 맞게 구조화해 모아 둔 데이터의 집합으로, 필요할 때 데이터를 쉽게 검색하고 추가하고 고치고 지울 수 있도록 설계되어 있습니다. 데이터베이스를 관리하는 소프트웨어를 데이터베이스 관리 시스템(database management system, DBMS)이라고 하며, DBMS는 사용자와 데이터 사이에서 데이터를 만들고 관리하고 찾는 창구 역할을 합니다.

데이터베이스에는 흔히 네 가지 성질이 기대됩니다. 서버가 다시 시작해도 데이터가 사라지지 않는 영구성(persistence), 정해 둔 규칙에 따라 데이터가 정확하게 유지되는 일관성(consistency), 여러 사용자가 동시에 같은 데이터를 바꿀 때 충돌을 막는 동시성 제어(concurrency control), 애플리케이션과 데이터를 분리해 한쪽의 변경이 다른 쪽에 미치는 영향을 줄이는 데이터 독립성입니다.

다만 이 성질들은 DBMS라는 이름만으로 저절로 보장되지 않습니다. 영구성은 실제 저장 방식과 커밋, 복구 설정에 달려 있으며, 예를 들어 Redis는 디스크에 기록하는 기능을 완전히 끄는 설정도 제공합니다(Redis의 지속성 문서). 데이터의 정확성은 선언해 둔 제약과 애플리케이션의 검증으로 지켜야 하고, 동시에 일어나는 갱신은 트랜잭션(transaction)과 격리 수준을 정해 처리해야 합니다. MySQL의 커밋과 롤백 조건과 트랜잭션 격리 수준을 알아야 각 작업을 언제 확정하고 동시 요청을 어떻게 다룰지 설명할 수 있습니다.


데이터베이스의 종류

데이터베이스는 크게 관계형 데이터베이스와 NoSQL 데이터베이스로 나눕니다. 둘을 가르는 기준은 데이터를 어떤 모양으로 저장하고 다루느냐입니다. 관계형은 미리 정한 열을 가진 테이블에 행을 쌓고 SQL로 다루며, NoSQL은 그 모델을 따르지 않고 데이터에 맞는 다른 모양을 씁니다. 아래에서 각각의 특징과 대표 제품을 봅니다.

관계형 데이터베이스

관계형 데이터베이스 관리 시스템(relational database management system, RDBMS)은 가장 오래되고 널리 쓰이는 종류입니다. 데이터를 스프레드시트처럼 테이블(table)과 행(row), 열(column)로 구성하고, SQL(Structured Query Language)로 데이터를 다룹니다. 행을 구별하는 기본 키(primary key)와 다른 테이블의 행을 참조하는 외래 키(foreign key)를 설계할 수 있는데, 모든 테이블에 두 제약이 저절로 생기는 것은 아니며 기본 키가 없는 테이블도 만들 수 있습니다(MySQL의 CREATE TABLE 문서).

관계형 데이터베이스는 중복을 줄이고 정확성을 지키기 위해 데이터를 여러 테이블로 나누는 정규화(normalization)를 하고, 테이블을 만들 때 열의 이름과 타입 같은 구조(스키마, schema)를 미리 정합니다. 또 원자성(atomicity), 일관성(consistency), 격리성(isolation), 지속성(durability)을 뜻하는 ACID 트랜잭션을 지원해, 여러 변경을 모두 성공시키거나 모두 취소할 수 있습니다. 그래서 금융처럼 데이터의 정확성이 핵심인 시스템에서 많이 씁니다. 정규화와 트랜잭션은 데이터베이스 교재의 정규화와 함수 종속과 트랜잭션과 멱등성에서 자세히 다룹니다.

관계형 데이터베이스는 데이터의 일관성과 정확성을 지키기 좋고, 정해진 형태의 데이터와 관계가 복잡한 데이터에 잘 맞으며, 오래 쓰여 안정적이고 성숙한 기술입니다. 반면 스키마를 바꾸기가 덜 유연하고, 여러 서버로 나눠 늘리는 수평 확장(scale-out)이 상대적으로 어려워 한 서버의 성능을 높이는 수직 확장(scale-up)을 주로 씁니다. 정해진 형태가 아닌 데이터를 저장할 수 있는지와 그 조회 비용은 DBMS에 따라 다른데, 예를 들어 PostgreSQL은 JSON과 JSONB 타입을 지원하므로(PostgreSQL의 JSON 타입 문서) JSON 데이터라는 이유만으로 관계형 데이터베이스를 빼지는 않습니다.

대표적인 관계형 데이터베이스로는 가장 널리 쓰이는 오픈 소스인 MySQL, 기능이 풍부하고 확장성이 좋은 오픈 소스 PostgreSQL, 기업 환경에서 많이 쓰는 상용 제품 Oracle, Microsoft의 SQL Server, 서버 없이 파일 하나에 데이터를 저장해 개발과 작은 앱에 알맞은 SQLite가 있습니다.

NoSQL 데이터베이스

NoSQL(Not Only SQL) 데이터베이스는 관계형 모델을 따르지 않는 데이터베이스로, 스키마의 유연성과 확장성 문제를 풀기 위해 나왔습니다. 데이터를 저장하는 방식에 따라 여러 종류로 나뉘고, 조회하는 언어나 API도 데이터베이스마다 다릅니다.

NoSQL은 데이터의 구조를 미리 엄격하게 정하지 않는 유연한 스키마를 쓰는 경우가 많아, 데이터 모델을 빠르게 바꿀 수 있습니다. 여러 서버에 데이터를 나눠 저장하는 분산 환경에서 확장하기 쉽고, 텍스트나 로그처럼 정해진 형태가 아닌 데이터도 효율적으로 다룹니다. 반면 데이터의 일관성 보장이 관계형 데이터베이스보다 약할 수 있고, 제품에 따라 관계형 데이터베이스보다 성숙도가 낮을 수 있습니다.

분산 데이터베이스를 이야기할 때 자주 나오는 CAP 정리(CAP theorem)는, 서버 사이의 네트워크가 끊겼을 때 모든 복사본이 하나처럼 최신 순서를 지키는 일관성(C)과, 실패하지 않은 서버가 모든 요청에 응답하는 가용성(A)을 동시에 보장할 수는 없다는 제약입니다. SQL인지 NoSQL인지라는 이름만으로 네트워크가 끊겼을 때 어떻게 동작할지가 정해지지는 않습니다.

NoSQL의 대표적인 종류는 네 가지입니다. 문서 지향(document-oriented) 데이터베이스는 JSON이나 BSON(binary JSON) 같은 문서 형태로 데이터를 저장하며, MongoDB와 CouchDB가 있습니다. 키-값(key-value) 데이터베이스는 키와 값의 쌍으로 데이터를 저장하는 가장 단순한 형태로, Redis와 DynamoDB가 있습니다. 와이드 컬럼(wide-column) 데이터베이스는 행 키와 컬럼 패밀리를 중심으로 데이터를 구성하며 Cassandra와 HBase가 대표적인데, 분석용으로 열 단위로 저장하는 방식과는 다른 분류입니다. 그래프(graph) 데이터베이스는 노드와 엣지로 데이터 사이의 관계를 그래프로 나타내며, Neo4j와 Amazon Neptune이 있습니다.

문서형이나 키-값 모델에서 여러 데이터의 관계를 반복해 결합하려면 추가 조회나 중복 데이터를 관리하는 비용이 생길 수 있습니다. 하지만 그래프 데이터베이스는 오히려 관계와 경로 탐색을 중심으로 모델링하므로(Neo4j의 그래프 데이터베이스 개념), NoSQL 전체가 복잡한 관계에 맞지 않는다고 볼 수는 없습니다.


데이터베이스 고르기

접근 패턴 기반 데이터베이스 선택

FLOWCHART · B99 WEB FUNDAMENTALS

접근 패턴 기반 데이터베이스 선택

저장소는 access pattern, relationship, integrity and transaction boundary, consistency, scaling and operations로 선택한다. 클라이언트는 DB에 직접 접근하지 않고 서버가 입력·권한·transaction과 공개 응답을 통제한다.

지배적 접근 패턴으로 고르는 데이터 저장소 무결성 조인, 문서 aggregate, 키 조회, 관계 탐색, 텍스트 관련도, 복수 패턴 가운데 가장 강한 요구를 기준으로 관계형·문서·키값·그래프·검색·혼합 저장소를 고르는 라우터 가장 지배적인 조회·무결성 요구는?데이터 모양이 아니라 access pattern과 운영 비용으로 선택관계형transaction · FKjoin이 핵심문서aggregate 단위함께 읽고 쓰기키–값키로 즉시 조회cache · session그래프다단 관계 탐색경로가 질의검색 인덱스전문 검색 · 관련도원본 DB와 분리혼합복수 패턴이 필수동기화 책임 명시PROTOTYPE WITH REAL QUERIES · MEASURE · REVISIT
지배적 접근 패턴으로 고르는 데이터 저장소지배적인 조회·무결성 요구에서 관계형, 문서, 키–값, 그래프, 검색 인덱스, 혼합으로 각각 연결된다. 각 연결의 목적지는 지배적인 접근 패턴과 운영 비용에 따라 선택한다. 실제 질의로 시험하고 측정한 뒤 재검토한다. PROTOTYPE WITH REAL QUERIES · MEASURE · REVISIT 가장 지배적인조회·무결성요구는?데이터 모양이아니라 accesspattern과 운영비용으로 선택 관계형transaction · FKjoin이 핵심 문서aggregate 단위함께 읽고 쓰기 키–값키로 즉시 조회cache · session 그래프다단 관계 탐색경로가 질의 검색 인덱스전문 검색 ·관련도원본 DB와 분리 혼합복수 패턴이 필수동기화 책임 명시

저장소 이름부터 고르지 않는다. 실제 query와 불변식을 먼저 적고, 둘 이상의 저장소를 쓰면 동기화 실패도 설계한다.

위 그림은 데이터를 어떻게 읽고 쓰는지, 즉 접근 패턴(access pattern)을 기준으로 저장소를 고르는 지도입니다. 출발점의 질문은 가장 중요한 조회와 정확성의 요구가 무엇인지이며, 데이터의 모양이 아니라 접근 패턴과 운영 비용으로 고릅니다. 트랜잭션과 외래 키, 여러 테이블의 결합(join)이 핵심이라면 관계형을, 하나의 묶음(aggregate)을 함께 읽고 쓴다면 문서형을, 키로 바로 찾는 캐시나 세션이라면 키-값을 고릅니다.

여러 단계를 거치는 관계 탐색 자체가 조회라면 그래프 데이터베이스를, 글 전체에서 검색하고 관련도를 따져야 한다면 원본 데이터베이스와 따로 두는 검색 색인(search index)을 씁니다. 여러 패턴이 모두 꼭 필요하다면 저장소를 섞어 쓰되, 저장소 사이의 동기화를 누가 어떻게 맞출지 분명히 정합니다. 어느 쪽이든 실제 조회로 시험해 보고, 측정하고, 필요하면 다시 검토합니다.

그림 아래의 설명처럼 저장소의 이름부터 고르지 말고, 실제 조회와 지켜야 할 규칙을 먼저 적어야 합니다. 둘 이상의 저장소를 쓴다면 동기화가 실패했을 때의 처리까지 설계해야 합니다. 또 클라이언트는 데이터베이스에 직접 접근하지 않고, 서버가 입력과 권한, 트랜잭션과 공개할 응답을 통제합니다.


웹 애플리케이션에서 데이터베이스의 역할

백엔드 서버와 데이터베이스는 함께 일합니다. 서버는 회원 가입이나 게시물 작성, 상품 등록처럼 클라이언트에게서 받은 데이터를 데이터베이스에 저장하고, 요청에 따라 게시물 목록이나 사용자 정보 같은 데이터를 조회해 응답합니다. 게시물 수정이나 프로필 변경처럼 기존 데이터를 고치고, 회원 탈퇴나 게시물 삭제처럼 필요 없는 데이터를 지웁니다. 재고 관리나 주문 처리, 권한 부여 같은 업무 규칙을 실행할 때도 데이터베이스의 데이터를 쓰거나 바꿉니다.

Node.js와 Express로 만든 백엔드 서버가 데이터베이스와 직접 통신하고, 프런트엔드는 백엔드의 API를 통해서만 데이터베이스의 데이터에 간접적으로 접근합니다.

API와 영속성 계층의 신뢰 경계

DATA-FLOW

API와 영속성 계층의 신뢰 경계

외부 입력과 저장 데이터는 서로 다른 계약입니다. 입력·권한을 검증한 뒤 업무 규칙을 적용하고, 저장 결과 중 공개할 필드만 응답으로 돌려줍니다. 그림은 역할을 나눈 설계 예시입니다.

API와 영속성 계층의 신뢰 경계 Untrusted HTTP, application trust boundary, persistence 영역을 나누고 client request DTO에서 HTTP adapter, validation과 authorization, service, repository transaction, database로 흐른 뒤 storage와 response mapper를 거쳐 public DTO로 돌아오는 경로를 표시한다. Clientuntrusted JSONHTTP adapterroute · statusValidate + authschema · identityService / domainbusiness ruleRepositorytransactionDatabasestored rowStorage mapperrow ↔ domainResponse mapperpublic fields onlyPublic DTOstatus · safe bodyUNTRUSTED HTTPVALIDATED DOMAIN BOUNDARYPERSISTENCErequest DTOparsedomain commandtransactionSQLpublic response DTO실패 → rollback
API와 영속성 계층의 신뢰 경계Client 입력은 HTTP adapter, 검증과 권한, 업무 규칙, repository와 database로 이어진다. 저장 결과는 storage와 response mapper, public DTO를 거쳐 adapter와 Client로 반환된다. Repository 실패는 rollback한다. 요청, 검증된 업무, 영속성의 신뢰 경계를 구분한다. UNTRUSTED HTTP VALIDATED DOMAINBOUNDARY PERSISTENCE VALIDATED DOMAINBOUNDARY request DTO parse domaincommand transaction SQL public responseDTO 실패 → rollback 응답 Clientuntrusted JSON HTTP adapterroute · status Validate + authschema · identity Service / domainbusiness rule Repositorytransaction Databasestored row Storage mapperrow ↔ domain Responsemapperpublic fields only Public DTOstatus · safe body
  • 요청 command
  • 저장 결과·공개 응답
  • rollback

트랜잭션은 여러 변경을 하나로 묶을 때 사용합니다. DB row의 비공개 필드가 응답에 섞이지 않도록 두 방향의 경계 변환을 명시합니다.

위 그림은 메모리 배열을 데이터베이스로 바꿨을 때 요청이 거치는 구간을 세 영역으로 나눠 보여 줍니다. 왼쪽의 믿을 수 없는 HTTP 영역에서는 클라이언트가 보낸 JSON을 HTTP 어댑터가 받아, 라우트를 고르고 상태 코드를 정합니다. 가운데의 검증된 영역에서는 입력의 형식과 사용자의 신원, 권한을 확인한 뒤, 서비스가 업무 규칙을 적용해 처리할 명령을 만듭니다. 오른쪽의 영속성 영역에서는 리포지토리(repository)가 트랜잭션 안에서 SQL을 실행해 데이터베이스의 행을 저장하거나 읽습니다.

돌아오는 길에는 저장 매퍼(storage mapper)가 데이터베이스의 행을 애플리케이션의 데이터로 바꾸고, 응답 매퍼(response mapper)가 그 가운데 공개할 필드만 골라 응답 객체(DTO)를 만들어 상태 코드와 안전한 본문으로 돌려줍니다. 리포지토리에서 실패하면 트랜잭션을 롤백(rollback)해 일부만 저장되지 않게 합니다. 그림 아래의 설명처럼 트랜잭션은 여러 변경을 하나로 묶을 때 쓰며, 비밀번호 해시처럼 데이터베이스의 행에만 있어야 할 필드가 응답에 섞이지 않도록 들어오는 방향과 나가는 방향 모두에서 데이터를 명시적으로 바꿉니다.


SQL 기초

가장 널리 쓰이는 관계형 데이터베이스 언어인 SQL의 기본 문법을 간단히 살펴봅니다. 다음 코드는 MySQL 8.4 문법입니다. USE, AUTO_INCREMENT, DATETIME 같은 표기는 모든 DBMS에서 그대로 통하는 공통 문법이 아니므로, PostgreSQL이나 SQLite에 붙여 넣기 전에 그 DBMS의 데이터베이스 선택, 자동 증가, 날짜 타입 문법을 확인하세요.

SQL을 실행하려면 MySQL 서버에 연결된 SQL 콘솔이나 편집기가 필요하며, Node.js의 터미널이나 브라우저 주소창에 입력하는 것이 아닙니다. 다음 순서는 새 실습용 데이터베이스 mydatabase에 아직 users 테이블이 없다고 가정하며, 계정에 데이터베이스와 테이블을 만들고 조회, 추가, 수정, 삭제할 권한이 있어야 합니다. 같은 이름의 데이터베이스나 테이블이 이미 있는 환경에서 반복 실행하는 방법은 따로 정해야 합니다.

아래의 행 변화는 자동 증가의 시작값과 간격이 기본값인 1이고, 다른 쓰기나 실패한 삽입 없이 두 INSERT를 한 번씩 실행하며, 각 문장이 바로 확정되는 자동 커밋(autocommit)이 켜져 있다고 가정해 예측합니다. created_at의 실제 시각은 실행할 때 정해집니다(MySQL의 데이터베이스 생성, 자동 증가 예제).

먼저 데이터베이스를 만들고 선택합니다.

CREATE DATABASE mydatabase; -- 데이터베이스 생성
USE mydatabase;            -- 데이터베이스 선택

USE로 선택해 두면 이후의 문장은 모두 mydatabase 안에서 실행되므로, 테이블 이름 앞에 데이터베이스 이름을 붙이지 않아도 됩니다.

테이블을 만듭니다. CREATE TABLE 뒤의 괄호 안에는 열마다 이름, 자료형, 제약 조건을 한 줄씩 적고, 줄 끝의 -- 뒤는 SQL의 주석입니다.

CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT, -- 정수형, 기본 키, 자동 증가
    username VARCHAR(50) NOT NULL UNIQUE, -- 문자열 50자, NULL 불허, 중복 불허
    email VARCHAR(100) NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP -- 기본값으로 현재 시간 설정
);

id는 자동으로 늘어나는 정수 기본 키이고, username은 비어 있을 수 없고 중복될 수 없는 50자 이하의 문자열이며, created_at은 값을 주지 않으면 지금 시각이 들어갑니다.

데이터를 추가(Create)합니다.

INSERT INTO users (username, email) VALUES ('alice', '[email protected]');
INSERT INTO users (username, email) VALUES ('bob', '[email protected]');

두 문장 모두 id와 created_at을 적지 않았으므로, 테이블을 만들 때 정한 대로 id는 자동으로 매겨지고 created_at에는 삽입한 시각이 들어갑니다.

데이터를 조회(Read)합니다. SELECT 뒤에는 가져올 열을, FROM 뒤에는 테이블을 적고, WHERE로 행을 고르며 ORDER BY로 순서를 정합니다. *는 모든 열이라는 뜻입니다.

SELECT * FROM users; -- 모든 사용자 조회
SELECT username, email FROM users WHERE id = 1; -- ID가 1인 사용자의 이름과 이메일 조회
SELECT * FROM users WHERE username LIKE 'a%'; -- 'a'로 시작하는 사용자 조회
SELECT * FROM users ORDER BY created_at DESC; -- 생성일 기준으로 내림차순 정렬

LIKE 'a%'의 %는 길이와 상관없이 아무 문자열이나 대신하는 기호이고, DESC는 내림차순, 즉 가장 최근에 만든 행부터 보여 달라는 뜻입니다.

데이터를 수정(Update)합니다. SET으로 바꿀 열과 새 값을 정하고, WHERE로 바꿀 행을 고릅니다.

UPDATE users SET email = '[email protected]' WHERE id = 1; -- ID가 1인 사용자의 이메일 변경

WHERE를 빠뜨리면 모든 행의 이메일이 같은 값으로 바뀝니다. 그래서 실행하기 전에 같은 조건으로 SELECT를 먼저 해 보고 바뀔 행을 확인하는 습관이 안전합니다.

데이터를 삭제(Delete)합니다. DELETE도 WHERE를 빠뜨리면 테이블의 모든 행이 지워집니다.

DELETE FROM users WHERE id = 2; -- ID가 2인 사용자 삭제

각 문장은 앞 단계가 성공한 뒤에 실행합니다. 새 users 테이블은 처음에 0행입니다. 두 INSERT 뒤에는 id=1, username=alice, [email protected]과 id=2, username=bob, [email protected]의 2행이 있고, 이때 WHERE id = 1과 LIKE 'a%'는 Alice 한 행을 고릅니다. UPDATE ... WHERE id = 1 뒤에도 행은 2개이고 Alice의 이메일만 [email protected]으로 바뀌며, DELETE ... WHERE id = 2 뒤에는 Alice 한 행만 남습니다. 이것은 결과 행의 값을 예측한 것이며, 콘솔에 나오는 실행 시간이나 출력 순서까지 정해진 것은 아닙니다.

SELECT * FROM users는 행의 순서를 보장하지 않습니다. ORDER BY created_at DESC도 두 시각이 같으면 그 두 행의 순서는 정해지지 않으므로, 순서까지 비교하려면 ORDER BY created_at DESC, id DESC처럼 정렬 조건을 더해야 합니다. 자동 증가 ID도 빈틈없는 번호를 보장하지는 않으며, 여기서 1과 2를 기대하는 것은 새 테이블과 기본 설정, 한 번씩만 삽입한다는 조건 때문입니다.

실습을 마치면 테이블을 지웁니다. DELETE가 행만 지우는 것과 달리 DROP TABLE은 테이블 구조 자체를 없애므로, 그 뒤에 users를 조회하면 테이블이 없다는 오류가 납니다.

DROP TABLE users;

이런 SQL을 백엔드 서버에서는 데이터베이스 드라이버나 ORM(object-relational mapping) 라이브러리로 실행해 데이터를 다룹니다.

더 깊이: AUTO_INCREMENT의 번호에는 빈자리가 생긴다

앞의 행 변화는 실패한 삽입이 없다고 가정했는데, 실제 서비스에서는 이 가정이 쉽게 깨집니다. MySQL 8.4의 InnoDB 문서에 따르면, AUTO_INCREMENT 값은 한 번 만들어지면 그 INSERT가 끝까지 실행되었는지, 그 문장을 담은 트랜잭션이 되돌려졌는지와 상관없이 되돌려지지 않으며, 이렇게 잃은 번호는 다시 쓰지 않습니다. 그래서 중복된 username 때문에 삽입이 실패하거나 트랜잭션을 롤백하면 다음 행의 id는 건너뛴 번호가 됩니다.

여러 행을 한 번에 넣는 문장에서도 빈자리가 생길 수 있습니다. MySQL 8.4의 기본 잠금 모드인 innodb_autoinc_lock_mode=2(interleaved)는 여러 삽입이 동시에 번호를 받게 해 처리량을 높이는 대신, 동시에 실행되는 대량 삽입 사이에서 번호가 연속되지 않을 수 있습니다. 또 문장이 실제로 몇 개의 번호를 쓸지 미리 알 수 없으면 넉넉히 잡아 두므로, 다음 문장과의 사이에 빈 번호가 남기도 합니다.

그래서 id는 행을 구별하는 값으로만 쓰고, 번호가 1씩 연속된다고 가정한 코드를 쓰지 않습니다. 가입 순서나 개수가 필요하면 created_at으로 정렬하거나 COUNT(*)로 셉니다. 자세한 규칙은 MySQL 8.4의 AUTO_INCREMENT 처리 설명에 있습니다.


정리

  • 데이터베이스는 서버를 다시 시작해도 데이터를 보관하고, 여러 사용자의 동시 접근을 다루는 백엔드의 저장소이며, DBMS가 이를 관리합니다.
  • 영구성, 정확성, 동시성 제어는 DBMS의 이름만으로 보장되지 않고, 저장 설정, 제약과 검증, 트랜잭션과 격리 수준으로 지켜야 합니다.
  • 관계형 데이터베이스는 테이블과 SQL, 스키마와 ACID 트랜잭션으로 정확성과 복잡한 관계에 강하고, NoSQL은 문서, 키-값, 와이드 컬럼, 그래프 같은 모델로 유연성과 확장성에 강합니다.
  • 저장소는 이름이 아니라 가장 중요한 접근 패턴과 지켜야 할 규칙, 운영 비용을 기준으로 고르고, 실제 조회로 시험해 측정합니다.
  • 서버는 들어온 입력을 검증하고 트랜잭션으로 저장한 뒤, 공개할 필드만 응답으로 돌려주며, 클라이언트는 데이터베이스에 직접 접근하지 않습니다.
  • SQL의 INSERT, SELECT, UPDATE, DELETE로 데이터를 다루며, 순서가 필요하면 ORDER BY로 정렬 조건을 정합니다.

연습 문제

1. 조회 결과 예측하기

두 INSERT와 위의 UPDATE, DELETE를 차례로 마친 상태에서, 다음 두 조회는 각각 몇 행과 어떤 이메일을 돌려줄까요? 테이블 삭제는 아직 하지 않았습니다.

SELECT id, username, email FROM users WHERE id = 2;
SELECT id, username, email FROM users WHERE username LIKE 'a%';
해설 보기

첫 조회는 Bob을 이미 지웠으므로 0행입니다. 두 번째 조회는 id=1, username=alice, [email protected]인 한 행입니다.

UPDATE는 조건에 맞는 기존 행의 값만 바꾸고, DELETE는 조건에 맞는 행을 없앴으므로 두 작업의 차이가 조회 결과에 그대로 드러납니다.

2. WHERE를 빠뜨린 UPDATE

누군가 Alice의 이메일만 바꾸려다 UPDATE users SET email = '[email protected]';처럼 WHERE를 빠뜨렸습니다. 두 행이 있는 테이블에서 이 문장을 실행하면 어떻게 될까요? 이런 사고를 줄이려면 어떻게 해야 할까요?

해설 보기

조건이 없으므로 모든 행의 이메일이 [email protected]으로 바뀌어, Bob의 이메일도 같은 값이 됩니다. 자동 커밋이 켜져 있다면 이 변경은 바로 확정됩니다.

바꾸기 전에 같은 조건으로 SELECT를 먼저 실행해 대상 행을 확인하고, 중요한 변경은 트랜잭션 안에서 실행해 결과를 확인한 뒤 커밋하거나 롤백하는 습관을 들입니다. 애플리케이션에서는 사용자가 입력한 값으로 조건을 만들 때 대상이 정확히 하나인지 확인하는 검사도 둡니다.

3. 저장소 고르기

다음 세 가지 데이터는 각각 어떤 저장소가 알맞을까요? ① 로그인 세션을 키로 빠르게 찾아야 하는 데이터, ② 주문을 만들면서 결제 기록과 재고 감소를 함께 성공시키거나 함께 취소해야 하는 데이터, ③ 상품 설명 전체에서 검색어와의 관련도 순으로 찾아야 하는 데이터.

해설 보기

①은 키로 바로 찾는 패턴이므로 Redis 같은 키-값 저장소가 알맞습니다. 세션을 잃어도 다시 로그인하면 되는지, 꼭 보존해야 하는지에 따라 영구성 설정도 정합니다.

②는 여러 변경을 하나로 묶는 트랜잭션과 정확성이 핵심이므로 관계형 데이터베이스가 알맞습니다. ③은 전체 글 검색과 관련도가 필요하므로 원본 상품 데이터와 따로 둔 검색 색인을 쓰고, 상품이 바뀔 때 색인을 어떻게 갱신할지도 함께 정합니다.

이것으로 12장을 마칩니다. 12장에서는 클라이언트와 서버의 관계에서 시작해, Node.js와 Express로 서버를 만들고, REST 원칙에 맞춰 API를 설계하고 입력을 검사하며, 데이터베이스의 종류와 SQL의 기초까지 백엔드의 기본을 살펴봤습니다. 다음 장의 웹 보안 기초 (HTTPS, XSS, CSRF)부터는 HTTPS와 XSS, CSRF 같은 웹 보안의 기초를 시작으로, 인증과 권한 관리, 웹 성능 최적화의 원칙과 에셋 최적화를 다룹니다.