본문으로 건너뛰기 데이터베이스 완전 가이드 — ACID·격리·인덱스·최적화·운영

데이터베이스 완전 가이드 — ACID·격리·인덱스·최적화·운영

데이터베이스 완전 가이드 — ACID·격리·인덱스·최적화·운영

이 글의 핵심

관계형 데이터베이스의 동작 원리를 엔진 관점에서 정리합니다. ACID가 로그·버퍼·잠금으로 어떻게 구현되는지, 격리 수준과 이상 현상, B-tree와 해시 인덱스의 역할, 옵티마이저 기반 쿼리 튜닝의 흐름, 그리고 실서비스에서 쓰는 연결 풀·복제·관측 패턴을 다룹니다.

들어가며

관계형 데이터베이스(RDBMS)는 SQL뿐 아니라 스토리지, 버퍼 풀, 쓰기 로그(WAL), 동시성 제어가 결합된 시스템입니다. API처럼 “쿼리를 보내면 결과가 나온다”는 수준을 넘어, 운영·튜닝·장애 분석을 하려면 엔진이 ACID와 격리를 어떻게 보장하는지, 인덱스가 실행 계획에 어떻게 연결되는지를 이해하는 것이 필요합니다.

이 글은 특정 제품 매뉴얼이 아니라, PostgreSQL·MySQL(InnoDB) 등에서 공통적으로 등장하는 개념을 중심으로 정리합니다. 실제 설정값은 버전·환경에 따라 다르므로, 원리를 잡은 뒤 공식 문서와 EXPLAIN 결과로 검증하는 절차를 권장합니다.


1. ACID 속성의 구현

ACID는 표준 프로토콜이 아니라 트랜잭션이 만족해야 할 성질을 네 가지로 나눈 것입니다. 구현체는 보통 로그 구조 + 버퍼 관리 + 잠금/MVCC의 조합으로 이를 달성합니다.

1.1 원자성(Atomicity)

모두 수행되거나 모두 수행되지 않아야 한다는 성질입니다. 디스크에 페이지를 바로 덮어쓰기만 하면 중간 상태가 남을 수 있으므로, 대부분의 엔진은 선행 기록 로그(write-ahead logging, WAL) 를 둡니다. 변경은 먼저 순차적인 로그 파일에 기록되고, 체크포인트·백그라운드 플러시를 통해 데이터 파일이 뒤따릅니다. 장애 시 재시작 복구는 로그를 재적용(redo) 하거나 완료되지 않은 트랜잭션을 되돌림(undo) 하는 방식으로 완성됩니다.

복구 시나리오를 조금 더 엔진에 가깝게 말하면, 재시작 후 redo(로그에 기록된 변경을 데이터 페이지에 반영)와 undo(미완료 트랜잭션이 남긴 변경을 되돌림)가 조합됩니다. 체크포인트(checkpoint) 는 “이 시점까지는 데이터 파일과 로그 상태가 일관된다”는 경계를 만들어, 재시작 시 재적용할 로그 범위를 줄입니다.

여러 노드에 걸친 갱신이 필요하면 2단계 커밋(2PC) 같은 분산 커밋 프로토콜이 등장하지만, 지연·장애 시 불완전한 트랜잭션 상태를 다루기 어렵고 운영 복잡도가 큽니다. 따라서 마이크로서비스에서는 가능하면 DB 단일 트랜잭션 또는 사가·이벤트 소싱·멱등 재시도로 경계를 나누는 설계가 흔합니다.

실무 관점: ORM이든 raw SQL이든, 트랜잭션 경계를 명확히 하고 실패 시 롤백이 보장되도록 해야 합니다. “일부만 커밋된 상태”는 애플리케이션 버그로 남기 쉽습니다.

1.2 일관성(Consistency)

데이터베이스 문맥에서 일관성은 두 층이 있습니다. (1) 제약 조건·외래 키·트리거로 스키마 수준에서 유지되는 불변식, (2) 비즈니스 규칙(예: “계좌 합계 불변”)은 애플리케이션이 트랜잭션으로 묶어 보장해야 하는 경우가 많습니다. DBMS는 유효한 상태에서 유효한 상태로만 이행되도록 돕지만, 모든 도메인 규칙을 DB만으로 표현하기는 어렵습니다.

1.3 격리성(Isolation)

동시에 실행되는 트랜잭션이 서로의 중간 결과를 얼마나 보지 못하게 할지는 격리 수준MVCC·잠금으로 구현됩니다. 다음 섹션에서 상세히 다룹니다.

1.4 지속성(Durability)

커밋이 성공하면 장애 후에도 그 커밋이 유지되어야 합니다. 이를 위해 WAL가 안정 저장소에 fsync(또는 그에 준하는 동기화)되기 전에 “커밋 완료”를 보고하는지, 그룹 커밋으로 배치 fsync를 하는지 등이 성능과 직결됩니다. 이중화·복제는 “단일 노드의 디스크 지속성”과는 별개로, 복제 지연페일오버 시 데이터 손실 창을 이해해야 합니다.


2. 트랜잭션 격리 수준

SQL 표준은 네 단계 격리를 정의합니다. 구현체마다 세부 동작과 기본값이 다르므로, “표준 이름”과 “우리 DB의 실제 동작”을 함께 확인해야 합니다.

2.1 네 가지 격리 수준과 이상 현상

격리 수준더티 리드비반복 읽기팬텀 리드
READ UNCOMMITTED허용될 수 있음가능가능
READ COMMITTED방지가능가능
REPEATABLE READ방지방지(구현에 따라 예외)구현 의존
SERIALIZABLE방지방지방지(직렬화 의미에 가깝게)
  • 더티 리드(dirty read): 아직 커밋되지 않은 다른 트랜잭션의 변경을 읽는 것입니다.
  • 비반복 읽기(non-repeatable read): 같은 행을 두 번 읽었을 때 값이 달라지는 것입니다.
  • 팬텀 읽기(phantom read): 범위 조건으로 읽었을 때, 다른 트랜잭션이 행을 추가·삭제하여 결과 집합이 달라지는 것입니다.

표준 표에 없는 변형 이상으로는 읽기 스큐(read skew)(서로 다른 행을 읽을 때 참조 무결성이 깨져 보이는 경우), 쓰기 스큐(write skew)(두 트랜잭션이 각각 다른 행을 읽고 각각 쓰면서 전역 불변식을 깨는 경우)가 있습니다. 이는 격리 수준만으로는 막히지 않을 수 있어 제약 조건·명시적 잠금·직렬화 가능한 트랜잭션 등을 함께 검토합니다.

2.2 MVCC와 스냅샷

다중 버전 동시성 제어(MVCC) 는 행의 여러 버전을 유지하며, 각 트랜잭션이 일관된 스냅샷을 읽도록 합니다. 읽기는 잠금을 최소화하며, 쓰기는 새 버전을 만들거나 잠금으로 직렬화하는 식으로 읽기·쓰기 경합을 완화합니다. 그러나 쓰기끼리 같은 행을 갱신할 때는 여전히 잠금·데드락이 발생합니다.

2.3 제품별 기본값(참고)

  • PostgreSQL: 기본 격리는 READ COMMITTED입니다. REPEATABLE READ는 스냅샷 기반으로 팬텀에 강하지만, 직렬화 이상을 완전히 막으려면 SERIALIZABLE과 직렬화 실패 재시도 전략을 검토합니다.
  • MySQL InnoDB: 기본은 REPEATABLE READ이며, InnoDB의 갭 락·넥스트 키 락 등으로 범위·팬텀을 제어하는 방식이 특징입니다.

실무: 격리 수준을 올리기 전에 실제로 어떤 이상이 문제인지부터 정의하세요. 보고서·정산처럼 읽기 스냅샷이 고정되어야 하면 트랜잭션 내 일관된 읽기가 필요하며, 경쟁적인 재고 감소에는 낙관적 락·비관적 락·명시적 SELECT … FOR UPDATE 등이 함께 고려됩니다.

격리 수준은 세션 단위로 설정할 수 있는 경우가 많습니다(제품별 문법 상이).

-- PostgreSQL 예: 이후 트랜잭션에만 적용
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- MySQL 예
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

운영 중 기본값을 바꾸기보다, 문제가 되는 배치·리포트 쿼리만 별도 격리·읽기 전용 복제본으로 분리하는 편이 안전합니다.


3. 인덱싱 전략: B-tree와 Hash

3.1 B-tree / B+tree가 널리 쓰이는 이유

대부분의 RDBMS 클러스터/세컨더리 인덱스B+tree(B-tree 계열)를 사용합니다. 디스크 블록 단위 I/O에 맞춰 트리 높이를 낮게 유지하며, 정렬된 키 순서 덕분에 범위 조건(BETWEEN, >, <), ORDER BY, 앞쪽 컬럼 일치(prefix) 검색에 유리합니다. 리프 레벨이 연결 리스트 형태인 경우가 많아 순차 스캔에도 활용됩니다.

3.2 Hash 인덱스

해시는 키를 해시 버킷에 매핑하여 등치 비교에서 O(1)에 가까운 탐색을 기대할 수 있습니다. 반면 범위 검색·정렬·부분 일치(앞부분만 일치) 에는 부적합하며, 충돌 처리·재해시 비용도 고려해야 합니다. PostgreSQL의 Hash 인덱스(버전에 따라 권장 시나리오가 다름), MySQL의 MEMORY 테이블 해시, 적응형 해시 인덱스(InnoDB의 보조 구조) 등은 워크로드가 등치 위주일 때 보조 수단이 됩니다.

3.3 설계 시 체크리스트

  1. 카디널리티: 선택도가 낮은 컬럼만 인덱스에 걸면 효과가 제한적입니다.
  2. 복합 인덱스 컬럼 순서: = 조건 → 범위 조건 순으로 두는 등, 자주 쓰는 필터 순정렬·그룹 요구를 함께 봅니다.
  3. 커버링 인덱스: 쿼리에 필요한 컬럼이 인덱스에만 있어 테이블 랜덤 접근을 줄이는지 검토합니다.
  4. 쓰기 비용: 인덱스는 삽입·갱신·삭제마다 유지 비용이 듭니다. 과도한 인덱스는 쓰기 병목을 만듭니다.

4. 쿼리 최적화의 기본 흐름

4.1 옵티마이저와 통계

옵티마이저는 통계(테이블·인덱스의 분포, 행 수, NULL 비율 등) 를 바탕으로 비용 모델로 실행 계획을 고릅니다. 통계가 오래되거나 부정확하면 잘못된 풀 스캔·잘못된 조인 순서가 나올 수 있어, 주기적인 ANALYZE(PostgreSQL) / ANALYZE TABLE(MySQL) 등이 운영 루틴에 포함됩니다.

4.2 실행 계획 읽기

EXPLAIN(및 실제 실행까지 포함하는 EXPLAIN ANALYZE 등)으로 접근 방식(시퀀셜 스캔, 인덱스 스캔, 인덱스 전용 스캔), 조인 알고리즘, 예상 행 수를 확인합니다. 비용 숫자 자체보다 상대 비교·병목 단계 식별에 초점을 두는 것이 좋습니다.

인덱스 전용 스캔(PostgreSQL의 Index Only Scan, MySQL에서 ExtraUsing index 등)은 테이블 힙을 거치지 않아 랜덤 I/O를 줄입니다. 다만 가시성 맵(VM)·클러스터 인덱스 여부에 따라 “거의만” 인덱스만 읽는 경우도 있어, 실행 계획을 반드시 확인해야 합니다.

4.3 애플리케이션 패턴

  • N+1 쿼리: ORM 루프마다 SELECT가 나가면 지연·부하가 폭증합니다. 배치 로딩·조인·DTO 설계로 줄입니다.
  • 불필요한 SELECT *: 네트워크·디코딩·캐시 오염을 키웁니다.
  • 큰 트랜잭션: 장시간 잠금을 유지하지 않도록 범위를 쪼갭니다.

자세한 MySQL 실행 계획 해석은 동일 블로그의 MySQL EXPLAIN 가이드와 함께 보시면 좋습니다.


5. 프로덕션 데이터베이스 패턴

5.1 연결 관리

애플리케이션은 매 요청마다 새 연결을 열면 비용이 큽니다. 연결 풀(PgBouncer, HikariCP, RDS Proxy 등)로 연결 수·대기 시간을 제어하며, 풀 크기는 CPU·DB max_connections·쿼리 지연과 함께 튜닝합니다.

5.2 읽기 확장과 일관성

읽기 복제본은 읽기 부하를 분산하지만 비동기 복제 지연이 있어, 방금 쓴 데이터를 바로 읽는 흐름에서는 스티키 세션이나 읽기 전 복제 지연 허용 정책이 필요할 수 있습니다.

5.3 가용성과 백업

자동 페일오버, 백업(풀·증분), 복구 목표 시간(RTO)·복구 시점(RPO) 을 문서화합니다. 마이그레이션은 호환 가능한 스키마 변경(expand–contract), 듀얼 라이트, 배치 백필 등으로 무중단에 가깝게 가져가는 패턴이 많습니다.

5.4 관측성

슬로우 쿼리 로그, pg_stat_statements, Performance Schema 등으로 상위 쿼리를 잡으며, 메트릭·트레이스로 “DB 한계인지 애플리케이션인지”를 구분합니다.

5.5 동시성과 비즈니스 규칙

낙관적 락(버전 컬럼), 유일 제약·UPSERT, 분산 락(Redis 등) 은 도메인에 따라 선택됩니다. DB가 제공하는 제약트랜잭션을 최대한 활용하되, 분산 환경에서는 “한 노드 안의 직렬화”만으로는 부족할 수 있음을 인지하는 것이 중요합니다.


정리

  • ACID는 WAL·undo·체크포인트·fsync·복제 등 저장·복구·동시성 메커니즘으로 구현됩니다.
  • 격리 수준은 이상 현상과 성능의 트레이드오프이며, MVCC는 읽기 경합을 줄이지만 쓰기 충돌은 별도로 다뤄야 합니다.
  • B+tree는 범위·정렬에 강하며, Hash는 등치 전문에 보조적으로 맞습니다.
  • 쿼리 최적화는 통계·실행 계획·애플리케이션 접근 패턴이 한 세트입니다.
  • 프로덕션에서는 연결 풀, 복제 지연, 백업·마이그레이션, 관측이 성능만큼 중요합니다.

데이터베이스를 “검증된 저장소”로 쓰려면, SQL 문법을 넘어 엔진이 약속을 지키는 방식을 아는 것이 장애 대응과 설계 논의의 공통 언어가 됩니다.


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. RDBMS 내부: ACID와 WAL·MVCC, 격리 수준, B-tree·해시 인덱스, 옵티마이저·실행 계획, 연결 풀·복제 등 프로덕션 패턴까지 한 번에 정리합니다. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

데이터베이스, 가이드, ACID·격리·인덱스·최적화·운영 등으로 검색하시면 이 글이 도움이 됩니다.