동시성 제어: 트랜잭션 충돌과 데이터 무결성을 다루는 방법
동시성 제어의 충돌 유형, 로킹·타임스탬프·MVCC·낙관적 제어 방식과 트랜잭션 격리 수준을 정리한다.
2026-08-14 · 최초 발행 2025-08-10
같은 데이터를 동시에 다룰 때 생기는 문제
다중 사용자 데이터베이스에서는 여러 트랜잭션이 같은 시점에 실행된다. 이때 각 작업이 서로의 중간 결과를 침범하지 않도록 만들어야 데이터 무결성과 비즈니스 로직을 지킬 수 있다.
동시성 제어는 트랜잭션의 ACID 특성 가운데 특히 고립성(Isolation)을 다룬다. 동일 데이터에 대한 동시 접근 문제를 제어하면서도 시스템 성능과 응답 시간을 함께 고려하는 것이 목적이다.
충돌이 드러나는 읽기와 쓰기
갱신 손실
두 트랜잭션이 같은 값을 읽은 뒤 각각 수정하면, 나중에 완료된 쓰기가 먼저 반영된 결과를 덮어쓸 수 있다.
오손 읽기
아직 커밋되지 않은 변경 내용을 다른 트랜잭션이 읽는 경우다. 변경을 수행한 트랜잭션이 롤백하면, 읽은 쪽은 유효하지 않은 값을 바탕으로 작업하게 된다.
반복 불가능 읽기
한 트랜잭션이 동일한 데이터를 다시 읽는 사이, 다른 트랜잭션이 값을 변경하고 커밋하면 두 읽기의 결과가 달라진다.
팬텀 읽기
조건 검색의 결과 집합 자체가 트랜잭션 도중 달라지는 문제다. 다른 트랜잭션이 해당 조건에 맞는 레코드를 추가하거나 삭제하면 처음과 다음 조회의 행 수가 달라질 수 있다.
잠금으로 접근 순서를 제어하는 방식
로킹은 트랜잭션이 데이터에 접근할 때 락을 확보해 다른 트랜잭션의 접근을 제한하는 방식이다.
공유 락(Shared Lock, S-Lock)은 읽기에 사용하며 여러 트랜잭션이 동시에 획득할 수 있다. 배타적 락(Exclusive Lock, X-Lock)은 쓰기에 사용하고 하나의 트랜잭션만 획득할 수 있다.
2단계 로킹 프로토콜(Two-Phase Locking)은 트랜잭션을 확장 단계와 수축 단계로 나눈다. 확장 단계에서는 락을 획득만 하고 해제하지 않으며, 수축 단계에서는 락을 해제만 하고 새 락을 획득하지 않는다.
이 프로토콜은 직렬 가능성(Serializability)을 보장하지만 데드락이 생길 수 있다.
타임스탬프와 버전으로 충돌을 다루는 방식
타임스탬프 기반 기법은 트랜잭션마다 고유한 타임스탬프를 부여하고, 이를 데이터 접근 순서의 기준으로 사용한다. 트랜잭션 시작 시 시스템 시간 기반 타임스탬프(TS)를 할당하며, 각 데이터 항목은 최근 읽기·쓰기 TS를 유지한다.
읽기에서 TS(T) < Write-TS(X)이면 트랜잭션 T는 롤백한다. 쓰기에서는 TS(T) < Read-TS(X) 또는 TS(T) < Write-TS(X)인 경우 트랜잭션 T를 롤백한다. 데드락이 발생하지 않는 대신 롤백이 빈번해질 수 있다.
다중 버전 동시성 제어(MVCC, Multi-Version Concurrency Control)는 데이터의 여러 버전을 유지해 읽기와 쓰기가 서로를 차단하는 일을 줄인다. 갱신마다 새 버전을 만들고, 트랜잭션은 시작 시점에 존재하던 가장 최근 버전의 데이터를 사용한다. PostgreSQL, Oracle, MySQL의 InnoDB 등 현대 DBMS에서 널리 사용한다.
MVCC는 읽기 작업의 성능을 높이고 잠금 경합을 줄이는 데 사용된다.
커밋 시점에 충돌을 검증하는 방식
낙관적 동시성 제어(Optimistic Concurrency Control)는 충돌이 드물게 발생한다고 가정한다. 트랜잭션 실행 중에는 데이터를 로컬 복사본에 저장해 작업하고, 커밋 시점에 다른 트랜잭션과 충돌했는지 검증한다. 충돌이 없으면 변경 사항을 반영하고, 충돌이 확인되면 재시도한다.
읽기 위주의 워크로드나 충돌이 적은 환경에서 효과적이다.
격리 수준이 허용하거나 막는 현상
SQL 표준의 트랜잭션 격리 수준은 동시성과 데이터 일관성 사이의 균형을 조절한다.
| 격리 수준 | 오손 읽기 | 반복 불가능 읽기 | 팬텀 읽기 | 성능 |
|---|---|---|---|---|
| READ UNCOMMITTED | 발생 | 발생 | 발생 | 매우 높음 |
| READ COMMITTED | 방지 | 발생 | 발생 | 높음 |
| REPEATABLE READ | 방지 | 방지 | 발생 | 중간 |
| SERIALIZABLE | 방지 | 방지 | 방지 | 낮음 |
순환 대기를 찾아 회복하는 방법
데드락은 둘 이상의 트랜잭션이 상대방이 점유한 자원을 기다리며 무한정 대기하는 상태다.
예방에는 타임스탬프 기반 Wait-Die/Wound-Wait 프로토콜을 사용할 수 있다. Wait-Die에서는 신규 트랜잭션이 대기하고 이전 트랜잭션은 중단한다. Wound-Wait에서는 신규 트랜잭션이 중단하고 이전 트랜잭션은 대기한다. 일정 시간 안에 끝나지 않은 트랜잭션을 롤백하는 시간 제한도 방법이 된다.
탐지와 회복에서는 대기 그래프(Wait-for Graph)의 주기를 확인한다. 데드락을 발견하면 롤백 비용이 가장 작거나, 변경한 데이터가 가장 적거나, 시작 뒤 경과 시간이 가장 짧은 트랜잭션을 희생자로 선택할 수 있다.
이 그래프에서 T1은 T2가 보유한 자원을 기다리고, T2는 T3의 자원을, T3는 다시 T1의 자원을 기다린다. 순환 대기가 성립한 상태다.
계좌 이체에서 갱신 손실을 막는 흐름
계좌 A에서 계좌 B로 이체할 때 동시성 제어가 없으면, 같은 잔액을 읽은 두 트랜잭션의 갱신이 서로 덮어써질 수 있다.
배타적 락을 사용하면 첫 트랜잭션이 계좌 A와 B에 대한 변경을 마친 뒤에 다음 트랜잭션이 진행할 수 있다.
재고 변경과 일관된 읽기
온라인 쇼핑몰 재고 관리에서는 MVCC로 각 사용자에게 일관된 뷰를 제공하면서 차단을 최소화할 수 있다.
워크로드의 충돌 빈도와 일관성 요구사항에 따라 로킹, 타임스탬프 기반 기법, MVCC, 낙관적 동시성 제어를 선택해야 한다. 격리 수준 역시 성능과 데이터 일관성의 요구를 함께 반영해 조정한다.