백엔드 개발자를 위한 데이터베이스 핵심 기술 심층 분석: 동시성 제어·격리수준·MVCC·데드락 완전 정복

백엔드 실무와 기술 면접의 핵심! 데이터베이스 데드락과 라이브락의 차이, 트랜잭션 4대 격리수준과 이상현상(Dirty Read, Phantom Read), MySQL InnoDB의 MVCC 동작 원리와 Undo/Redo 로그의 복구 메커니즘을 심층 분석합니다.
IT·과학
저자

Ikmyungterran

공개

2026년 9월 8일

Modified

2026년 9월 8일

0. 데이터베이스 동시성(Concurrency)의 본질: 줄서기와 은행 잔고

혼자만 쓰는 가계부 앱이라면 데이터베이스는 아무런 문제를 일으키지 않습니다. 내가 쓰고, 내가 읽으면 끝입니다.

하지만 사용자가 수백만 명인 쇼핑몰에서 “남은 재고가 단 1개인 한정판 스니커즈”를 동시에 1,000명이 ‘결제하기’ 버튼을 누른다면 어떤 일이 벌어질까요? * 데이터베이스가 순차적으로 하나씩 처리하자니 서버가 느려 터지고, * 여러 사람이 동시에 장부를 고치게 내버려두자니 재고가 마이너스로 떨어지는 참사(동시성 버그)가 터집니다.

데이터베이스 엔지니어링의 역사는 “어떻게 하면 데이터를 안전하게(무결성) 지키면서도, 최대한 많은 사람에게 빠르게(동시성) 서빙할 수 있는가”를 둘러싼 끝없는 줄타기였습니다.

flowchart LR
    A["동시성 제어의 딜레마"] --> B["완벽한 데이터 안전성 (ACID)"]
    A --> C["초고속 처리 성능 (Throughput)"]
    B ---|트레이드오프 Trade-off| C
    style A fill:#F1F5F9,stroke:#475569,stroke-width:2px;
    style B fill:#DCFCE7,stroke:#16A34A,stroke-width:2px;
    style C fill:#BAE6FD,stroke:#0284C7,stroke-width:2px;


1. 교착상태(Deadlock) vs 라이브락(Livelock): 좁은 골목길의 대치

1-1. 데드락(Deadlock)이란?

좁은 외나무다리에서 마주친 두 염소의 이야기와 같습니다: * 트랜잭션 A는 자원 1(테이블 A)을 잠근 뒤, 자원 2(테이블 B)를 얻으려고 기다립니다. * 트랜잭션 B는 자원 2(테이블 B)를 잠근 뒤, 자원 1(테이블 A)을 얻으려고 기다립니다. * 두 트랜잭션 모두 영원히 잠금을 풀지 않고 서로를 기다리며 멈춰버립니다.

graph LR
    subgraph Deadlock["데드락 (순환 대기)"]
        TxA["트랜잭션 A"] -->|"점유 (Lock)"| Res1["자원 1 (계좌 A)"]
        TxA -.->|"대기 (Wait)"| Res2["자원 2 (계좌 B)"]
        TxB["트랜잭션 B"] -->|"점유 (Lock)"| Res2
        TxB -.->|"대기 (Wait)"| Res1
    end
    style TxA fill:#FEE2E2,stroke:#DC2626,stroke-width:2px;
    style TxB fill:#FEE2E2,stroke:#DC2626,stroke-width:2px;
    style Res1 fill:#FEF3C7,stroke:#D97706,stroke-width:1px;
    style Res2 fill:#FEF3C7,stroke:#D97706,stroke-width:1px;

중요💡 실무 데드락 예방의 황금률

데드락을 원천 차단하는 가장 단순하고 강력한 방법은 “모든 트랜잭션이 여러 테이블이나 레코드를 수정할 때 항상 정해진 순서(예: Primary Key 오름차순)대로 락을 획득하게 만드는 것”입니다. A \(\rightarrow\) B 순서로만 락을 얻게 통일하면 순환 대기(Circular Wait) 고리가 절대 형성되지 않습니다.

1-2. 라이브락(Livelock)과의 결정적 차이

  • 데드락: 프로세스가 WAITING 상태에 빠져 CPU를 전혀 쓰지 않고 굳어있는 상태.
  • 라이브락: 복도에서 두 사람이 마주쳤을 때 양보하려고 서로 같은 방향으로 비켜서기를 무한 반복하듯, 상태는 계속 활발히 변경(RUNNING)되며 CPU를 100% 태우고 있지만 정작 아무런 실질적 작업도 전진하지 못하는 상태입니다.

2. 비관적 락(Pessimistic) vs 낙관적 락(Optimistic)

데이터 충돌을 예상하고 대처하는 철학에 따라 락의 방식이 완전히 갈립니다.

비교 항목 비관적 락 (Pessimistic Lock) 낙관적 락 (Optimistic Lock)
기본 철학 “충돌은 무조건 발생한다. 그러니 내가 먼저 독점하겠다.” “충돌은 거의 안 일어날 것이다. 일단 작업하고 커밋할 때 확인하자.”
구현 방식 데이터베이스 자체 락 메커니즘 (SELECT ... FOR UPDATE) 애플리케이션 레벨의 버전 컬럼 (UPDATE ... SET version = version + 1 WHERE version = 1)
적합한 환경 충돌 빈도가 매우 높고 데이터 정합성이 치명적인 금융 계좌 이체, 한정판 티켓팅 충돌 빈도가 낮고 읽기 위주인 게시판 글 수정, 블로그 포스팅
성능 영향 다른 트랜잭션이 대기하므로 동시 처리량(Throughput) 저하 위험 락 오버헤드가 없어 평소에는 초고속이나, 충돌 시 재시도(Retry) 비용 발생

3. 트랜잭션 격리 수준 (Isolation Levels)과 3대 이상현상

SQL 표준(ANSI/ISO SQL)은 동시성과 안전성의 타협점을 4단계의 격리 수준으로 정의합니다.

+--------------------------------------------------------------------------+
|          격리 수준이 높아질수록 안전성 UP! 동시 처리 속도는 DOWN!        |
|                                                                          |
|  [낮음] Read Uncommitted  -->  Dirty Read 발생                           |
|         Read Committed    -->  Non-Repeatable Read 발생                  |
|         Repeatable Read   -->  Phantom Read 발생 (MySQL은 MVCC로 방어)   |
|  [높음] Serializable      -->  이상현상 0건, 순차 실행(속도 최저)        |
+--------------------------------------------------------------------------+

3대 이상현상 명쾌한 비유:

  1. Dirty Read (더러운 읽기): A가 계좌 잔액을 100만 원에서 200만 원으로 올렸으나 아직 커밋(확정)하지 않았는데, B가 이를 읽고 200만 원인 줄 알고 행동함. 이후 A가 오류로 롤백해버리면 B는 유령 데이터를 본 셈이 됨.
  2. Non-Repeatable Read (반복 불가능한 읽기): B가 한 트랜잭션 안에서 잔액을 두 번 조회했는데, 그 1초 사이에 A가 값을 수정하고 커밋해버려 첫 번째 조회와 두 번째 조회의 잔액이 다르게 나옴.
  3. Phantom Read (유령 읽기): B가 WHERE age >= 30 조건으로 회원 수를 세었을 때 5명이었는데, 잠시 후 다시 세니 그사이 A가 35세 신규 회원을 INSERT하여 갑자기 6명으로 늘어나는 현상.

4. MVCC (다중 버전 동시성 제어): 읽기와 쓰기가 싸우지 않는 비결

과거 데이터베이스에서는 “누군가 데이터를 수정(쓰기)하고 있으면 다른 사람은 그 데이터를 읽을 수도 없었습니다(Shared Lock 대기).”

이 거대한 병목을 깨부순 혁신이 바로 MVCC (Multi-Version Concurrency Control)입니다. MySQL의 InnoDB 스토리지 엔진은 이를 기가 막히게 구현했습니다.

sequenceDiagram
    autonumber
    participant TxA as 트랜잭션 A (SELECT)
    participant Buffer as 메모리 버퍼 풀
    participant Undo as Undo 로그 (이전 버전 스냅샷)
    participant TxB as 트랜잭션 B (UPDATE)

    Note over Buffer: 원본: [ID: 1, Name: '철수']
    TxB->>Buffer: 1. Name을 '영희'로 UPDATE (커밋 전)
    Buffer->>Undo: 2. 옛날 데이터 [ID: 1, Name: '철수']를 Undo 세그먼트에 복사
    Note over Buffer: 메모리에는 [ID: 1, Name: '영희']가 기록됨
    TxA->>Buffer: 3. ID 1번 회원 조회 요청
    Note over TxA,Undo: 4. TxA는 자신의 시작 시점(Read View)보다 나중에 수정된 데이터를 무시함!
    Undo-->>TxA: 5. Undo 영역의 옛날 버전 [Name: '철수']를 읽어 반환!

  • 핵심 원리: 읽기 작업은 락(Lock)을 걸지 않습니다. 대신 데이터가 수정되면 이전 상태를 Undo Log(언두 로그)에 복사해 두고, 조회 트랜잭션에는 자신이 시작된 시점의 스냅샷(Read View)을 보여줍니다.
  • 결과: “쓰기 작업은 읽기 작업을 블로킹하지 않고, 읽기 작업도 쓰기 작업을 블로킹하지 않는다!” 이를 통해 대규모 동시 읽기 트래픽에서도 극강의 성능을 보장합니다.

5. 리두 로그(Redo) vs 언두 로그(Undo): 서버 전원이 나가도 데이터가 안 날아가는 이유

데이터베이스 서버의 전원 플러그가 갑자기 뽑히면(서버 크래시) 어떻게 될까요?

  • Redo Log (리두 로그 - 복구와 지속성 Guarantee):
    • “이미 저지른 일은 다시 해내서라도 지킨다.”
    • 트랜잭션이 커밋될 때 무거운 디스크 데이터 파일(ibd)을 직접 쓰는 것은 너무 느립니다. 대신 가벼운 연속 로그 파일(WAL: Write-Ahead Logging)에 변경 내역만 순차적으로 기록(Redo Log Flush)합니다.
    • 서버가 재부팅되면 Redo 로그를 읽어 미처 디스크에 저장되지 못한 변경 사항을 100% 복구(Roll-forward)합니다.
  • Undo Log (언두 로그 - 취소와 롤백 Guarantee):
    • “실패한 작업은 없었던 일로 되돌린다.”
    • 트랜잭션이 실행되는 도중 에러가 나거나 ROLLBACK이 호출되면, Undo 로그에 백업해 두었던 이전 데이터를 복원하여 원래대로 되돌립니다. 동시에 앞서 살펴본 MVCC의 과거 스냅샷 역할도 담당합니다.

6. 요약: 백엔드 개발자가 지켜야 할 DB 운영 5계명

  1. WHERE 절 없는 UPDATE/DELETE는 재앙이다: 안전 모드(sql_safe_updates)를 상시 켜두고 인덱스를 반드시 타게 하라.
  2. 트랜잭션의 범위를 가능한 한 짧게 유지하라: 트랜잭션 안에서 외부 네트워크 API(PG사 결제 호출 등)를 호출하는 것은 서버 전체를 데드락으로 몰고 가는 자살 행위다.
  3. 격리 수준을 불필요하게 Serializable로 올리지 마라: MySQL 기본값인 Repeatable Read나 대부분의 실무 표준인 Read Committed로도 비즈니스 로직(낙관적 락 결합)으로 안전하게 제어할 수 있다.
  4. 인덱스 컬럼을 함수로 가공하지 마라: WHERE YEAR(created_at) = 2026 대신 WHERE created_at >= '2026-01-01' 형태로 작성해야 B+Tree 인덱스를 탈 수 있다.
  5. Slow Query 로그를 주기적으로 분석하라: 인덱스 없는 풀 테이블 스캔(Full Table Scan)은 수백 명의 요청을 줄줄이 락 대기열로 몰아넣는다.

📚 참고 문헌 및 공식 출처

구분 자료명 주요 내용 링크
MySQL 바이블 Real MySQL 8.0 (1권 & 2권) 백은빈·이성욱 저, InnoDB 스토리지 엔진 내부 아키텍처 및 MVCC 완벽 해설 위키북스
데이터베이스 이론 Database System Concepts (Silberschatz 저) 트랜잭션 스케줄, 2PL 프로토콜, ARIES 복구 알고리즘 학술적 기초 Yale DB Group
공식 문서 MySQL 8.0 Reference Manual - InnoDB Locking and Transaction Model 격리 수준별 잠금 동작 및 데드락 감지 알고리즘 MySQL Official Docs
PostgreSQL 비교 PostgreSQL Concurrency Control (MVCC) PostgreSQL의 Tuple 힙 저장 및 VACUUM 아키텍처 비교 PostgreSQL Docs

함께 읽으면 좋은 글