데이터베이스 병행제어와 트랜잭션 격리 수준
병행제어가 갱신 손실과 더티 리드를 막는 방식, 로킹·MVCC·격리 수준·교착상태 관리 방법을 정리한다.
2026-08-14 · 최초 발행 2025-08-10
동시에 실행되는 트랜잭션이 만드는 문제
여러 사용자나 프로그램이 같은 데이터베이스에 동시에 접근하면 처리량과 자원 활용은 높아진다. 반면 동일 데이터를 읽고 바꾸는 작업이 겹치면 결과의 일관성과 무결성이 흔들릴 수 있다. 병행제어는 이 충돌을 관리하면서 동시성과 일관성의 균형을 맞추는 DBMS 핵심 기능이다.
대표적인 문제는 갱신 손실이다. 둘 이상의 트랜잭션이 같은 데이터를 수정할 때 마지막 갱신만 남고, 먼저 반영한 갱신이 사라지는 현상이다. 은행 계좌에 두 사용자가 동시에 입금하는 경우를 보면 흐름이 분명하다.
완료되지 않은 트랜잭션의 중간 결과를 다른 트랜잭션이 읽는 비완료 의존성도 문제다. 더티 리드라고도 하며, 원래 트랜잭션이 롤백되면 이미 그 값을 사용한 작업이 잘못된 데이터를 기준으로 처리될 수 있다.
트랜잭션이 실행되는 도중 다른 트랜잭션이 같은 데이터를 갱신하면 모순성 문제가 생긴다. 같은 쿼리를 두 번 실행했는데 서로 다른 결과가 나오는 비일관된 읽기도 여기에 포함된다. 또 한 트랜잭션의 롤백이 그 결과에 의존한 다른 트랜잭션의 롤백으로 이어지는 연쇄 복귀는 성능 저하와 자원 낭비를 유발한다.
충돌을 제어하는 방식
로킹은 트랜잭션이 데이터에 접근할 때 락을 설정해 다른 트랜잭션의 접근을 제한하는 일반적인 방식이다. 공유 락(Shared Lock, S-Lock)은 읽기 작업에 사용되며 다른 트랜잭션의 읽기는 허용하지만 쓰기는 허용하지 않는다. 배타적 락(Exclusive Lock, X-Lock)은 쓰기 작업에 사용되고, 다른 트랜잭션의 읽기와 쓰기를 모두 제한한다.
2단계 로킹 프로토콜(2PL: Two-Phase Locking)은 락을 얻기만 하는 확장 단계와 락을 반환하기만 하는 수축 단계로 나뉜다. 이 프로토콜은 직렬가능성(Serializability)을 보장하며, 엄격한 2PL(Strict 2PL)과 강한 엄격한 2PL(Rigorous 2PL) 같은 변형도 있다.
타임스탬프 기법(Timestamp Ordering)은 각 트랜잭션에 고유한 타임스탬프를 부여해 실행 순서를 정한다. 데이터 항목마다 읽기 타임스탬프(RTS)와 쓰기 타임스탬프(WTS)를 유지하며, Thomas의 쓰기 규칙(Thomas's Write Rule)으로 최적화할 수 있다. 교착상태를 막을 수 있다는 장점이 있지만, 롤백 빈도가 늘면 성능이 낮아질 수 있다.
낙관적 병행제어(Optimistic Concurrency Control)는 대부분의 트랜잭션이 충돌 없이 실행된다고 가정한다. 읽기 단계에서 임시 영역으로 데이터 연산을 수행하고, 검증 단계에서 다른 트랜잭션과의 충돌을 확인한 뒤, 통과하면 쓰기 단계에서 실제 데이터베이스에 반영한다. 로킹 오버헤드를 줄이고 높은 병행성을 얻을 수 있지만, 검증 실패에 따른 롤백은 자원 낭비가 될 수 있다.
다중버전 병행제어(MVCC: Multi-Version Concurrency Control)는 데이터 항목의 여러 버전을 유지한다. 읽기 트랜잭션이 쓰기 트랜잭션을 차단하지 않으며, 각 트랜잭션은 특정 시점의 데이터베이스 스냅샷을 볼 수 있다. PostgreSQL, Oracle, MySQL(InnoDB) 등 현대 DBMS에서 널리 사용되며 읽기-쓰기 충돌과 읽기 성능 문제를 줄이는 대신 저장 공간 증가와 버전 관리 오버헤드를 수반한다.
격리 수준이 허용하는 읽기의 범위
ANSI/ISO SQL 표준의 트랜잭션 격리 수준은 병행제어를 어느 강도로 적용할지 나타낸다.
READ UNCOMMITTED는 레벨 0이며 커밋되지 않은 데이터도 읽을 수 있다. 더티 리드, 비반복 읽기, 팬텀 읽기가 발생할 수 있고 성능은 높지만 일관성 보장은 약하다.
READ COMMITTED는 레벨 1로, 커밋된 데이터만 읽는다. 더티 리드는 막지만 비반복 읽기와 팬텀 읽기는 남는다. Oracle과 SQL Server 등을 포함한 대부분의 DBMS에서 기본 격리 수준으로 채택한다.
REPEATABLE READ는 레벨 2다. 트랜잭션 안에서 같은 쿼리를 여러 번 실행해도 같은 결과를 보장하며, 더티 리드와 비반복 읽기를 방지한다. 팬텀 읽기는 발생할 수 있고 MySQL InnoDB의 기본 격리 수준이다.
SERIALIZABLE은 레벨 3이며 완전한 직렬성을 보장한다. 더티 리드, 비반복 읽기, 팬텀 읽기를 모두 막지만 성능은 낮고 데이터 일관성은 완벽하게 보장한다.
서로의 락을 기다리는 상태를 다루는 법
교착상태(Deadlock)는 둘 이상의 트랜잭션이 상대방이 보유한 자원을 기다리며 무한정 대기하는 상태다. 로킹 기법을 사용할 때 주로 발생한다.
예방(Prevention)은 교착상태의 발생 조건 가운데 하나를 제거하는 방식이다. 필요한 락을 한 번에 모두 획득하는 One-shot locking, 자원에 순서를 부여하는 Resource ordering이 여기에 속한다. 다만 병행성과 자원 활용률이 낮아질 수 있다.
회피(Avoidance)는 자원을 할당할 때 안전 상태가 유지되도록 제어한다. 은행가 알고리즘(Banker's Algorithm)을 활용할 수 있지만, 사전에 자원 요구량을 알아야 하고 구현이 복잡하다.
탐지 및 회복(Detection and Recovery)은 교착상태 발생을 허용한 뒤 주기적으로 찾아 해결한다. 대기 그래프(Wait-for Graph)를 분석해 사이클을 탐지하고, 비용이 적은 트랜잭션을 희생자로 선정해 롤백하거나 일정 시간이 지나면 자동 롤백하는 타임아웃을 적용한다. 현대 DBMS에서 가장 많이 사용되는 방식이다.
DBMS마다 다른 구현 선택
Oracle Database는 MVCC를 구현하며 READ COMMITTED를 기본 격리 수준으로 사용한다. 락 관리자로 교착상태를 탐지하고 해결하며, 롤백 세그먼트로 언두(Undo) 정보를 관리한다.
MySQL(InnoDB)은 2단계 로킹과 MVCC를 결합한다. REPEATABLE READ가 기본 격리 수준이고, 자동 교착상태 탐지와 타임아웃 기반 해결을 제공한다. 갭 락(Gap Lock)으로 팬텀 읽기를 방지한다.
PostgreSQL은 스냅샷 기반 MVCC를 구현한다. READ COMMITTED를 기본 격리 수준으로 사용하며, 트랜잭션 ID와 튜플 가시성 규칙으로 버전을 관리하고 명시적 교착상태 감지 알고리즘을 적용한다.
SQL Server는 페시미스틱 로킹(Pessimistic Locking)을 중심으로 한다. READ COMMITTED가 기본 격리 수준이며, 락 호환성 매트릭스로 다양한 락 모드를 지원하고 락 에스컬레이션(Lock Escalation) 기능을 제공한다.
분산 환경으로 넓어지는 병행제어
분산 데이터베이스에서는 2단계 커밋 프로토콜(2PC: Two-Phase Commit), 팍소스(Paxos)와 라프트(Raft) 같은 합의 알고리즘, 결과적 일관성(Eventual Consistency) 모델이 병행제어와 맞닿아 있다.
NoSQL 데이터베이스는 CAP 이론에 따라 가용성과 일관성의 트레이드오프를 다룬다. 낙관적 병행제어 기법을 선호하고, 버전 벡터(Version Vector)로 충돌을 해결한다.
인메모리 데이터베이스에서는 락 프리(Lock-free) 자료구조, 소프트웨어 트랜잭션 메모리(STM) 기법, 멀티버전 기반의 시간 여행 알고리즘(Time Travel Algorithm)을 활용한다.
멀티코어와 멀티프로세서 환경의 확장성, 클라우드에서의 효율적인 병행제어 메커니즘, 트랜잭션 처리와 분석 처리를 함께 지원하는 HTAP(Hybrid Transaction/Analytical Processing) 시스템의 병행제어는 계속 다뤄야 할 과제다. 블록체인 같은 분산 원장 기술의 합의 알고리즘과 병행제어를 결합하는 문제도 남아 있다.