Day 5: 트랜잭션 & 동시성 제어
- ACID 특성
- Atomicity, Consistency, Isolation, Durability
- 동시성 제어 메커니즘
- Locking (Shared, Exclusive), Lock Escalation
- MVCC(Multi-Version Concurrency Control)
- 트랜잭션 격리 수준
- READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE
- 데드락(Deadlock) 및 해결 방안
- 교착 상태 조건 & 회피/탐지/복구 기법
데이터베이스에서 여러 사용자가 동시에 데이터를 읽고 쓰는 상황은 매우 흔하다. 트랜잭션을 통해 일련의 작업을 안전하게 처리하고, 동시성 제어 기법을 통해 여러 트랜잭션이 동시에 접근해도 데이터 무결성과 일관성을 보장해야 한다. 이를 위해 알아야 할 개념들을 정리해보자.
1. ACID 특성
트랜잭션이 안전하게 수행되기 위해 지켜야 할 4가지 성질을 요약한 것이 ACID이다.
-
Atomicity (원자성)
- 트랜잭션 내 모든 연산이 전부 성공하거나, 전부 실패(롤백)해야 한다.
- 예) A → B 계좌 이체 시, A에서 출금된 이후 B 계좌로 입금이 안 되면 안 된다.
-
Consistency (일관성)
- 트랜잭션이 완료되면, DB가 일관성 있는 상태(무결성 제약을 만족)로 유지되어야 한다.
- 예) 외래 키 규칙, CHECK 제약 등이 트랜잭션 후에도 깨지지 않아야 한다.
-
Isolation (고립성)
- 동시에 실행되는 트랜잭션끼리는 서로 간섭하면 안 된다(논리적으로는 순차적으로 실행된 것과 동일한 결과).
- 예) 다른 트랜잭션의 중간 작업 결과를 볼 수 없어야 한다.
-
Durability (지속성)
- 트랜잭션이 커밋 후에는, 시스템 장애가 발생해도 그 결과가 보장되어야 한다.
- 예) 로그 및 체크포인트를 통해 커밋된 변경 사항은 복구할 수 있어야 한다.
2. 동시성 제어 메커니즘
2.1 Locking (Shared, Exclusive), Lock Escalation
-
락(Lock)
- 트랜잭션이 특정 자원(테이블, 페이지, 행 등)을 사용할 때, 충돌을 방지하기 위해 잠그는 방법
- 공유 락(Shared Lock): 데이터를 읽을 때, 다른 트랜잭션이 읽기는 가능하지만 쓰기는 불가능
- 배타 락(Exclusive Lock): 데이터를 수정할 때, 다른 트랜잭션이 읽기/쓰기를 전혀 할 수 없음
-
Lock Escalation
- 특정 레벨(행 락)이 너무 많으면, DBMS가 상위 레벨(테 이블 락)로 락을 격상시켜 관리 비용을 줄이는 기법
- 너무 많은 세부 레벨 락이 걸리면, DBMS 성능에 오버헤드가 커질 수 있다.
주의: 과도한 락은 데드락과 성능 저하를 야기할 수 있으므로, 락 전략(락 범위, 락 지속 시간)을 잘 설계해야 한다.
2.2 MVCC (Multi-Version Concurrency Control)
- 개념:
- 데이터의 여러 버전을 저장해, 읽기 트랜잭션은 쓰기 트랜잭션과 충돌 없이 이전 버전 데이터를 읽도록 허용
- 예) InnoDB(MySQL), PostgreSQL 등에서 구현
- 장점:
- 읽기(SELECT)가 쓰기(UPDATE, DELETE) 락에 의해 차단되지 않아, 동시성 향상
- REPEATABLE READ와 같은 격리 수준을 구현하는 데 유리
- 단점:
- 오래된 버전 데이터를 보관하므로, 언젠가는 정리(가비지 컬렉션)가 필요하고 디스크 사용량이 늘어날 수 있음
3. 트랜잭션 격리 수준
여러 트랜잭션이 동시에 실행될 때 발생할 수 있는 Dirty Read, Non-Repeatable Read, Phantom Read와 같은 이상현상을 방지하기 위해 격리 수준을 설정한다. (SQL 표준)
-
READ UNCOMMITTED
- 다른 트랜잭션에서 아직 커밋되지 않은(미완료) 데이터를 읽을 수 있다(Dirty Read 가능)
- 거의 사용되지 않음
-
READ COMMITTED
- 다른 트랜잭션이 커밋한 내용만 읽을 수 있다(Dirty Read 방지)
- 그러나 동일 쿼리라도 트랜잭션 내에서 다른 시점에 실행하면, 값이 바뀔 수 있음(Non-Repeatable Read 가능)
-
REPEATABLE READ
- 트랜잭션이 시작된 시점의 특정 버전을 계속 보며, 동일 쿼리를 반복 실행해도 같은 결과(Non-Repeatable Read 방지)
- 다만, 팬텀(Phantom) 레코드 문제는 여전히 발생할 수 있음
-
SERIALIZABLE
- 트랜잭션이 마치 순차적으로 실행된 것처럼 완벽히 고립
- 팬텀 레코드도 방지(가장 엄격한 수준)
- 동시에 많은 트랜잭션이 실행되면 락 충돌이 많이 발생해 성능이 떨어질 수 있음
4. 데드락(Deadlock) 및 해결 방안
4.1 교착 상태(Deadlock) 조건
- 상호 배제(Mutual Exclusion): 락된 자원을 다른 트랜잭션이 동시에 사용할 수 없음
- 점유 및 대기(Hold and Wait): 자원을 점유한 상태에서 다른 자원을 기다림
- 비선점(Non-preemptive): 락을 강제로 해제할 수 없음
- 순환 대기(Circular Wait): 서로가 상호 교대로 필요한 락을 점유하여 사이클 형성
4.2 회피, 탐지, 복구 기법
- 회피(Avoidance)
- 자원 할당 순서를 엄격히 정해, 데드락 상황이 생기지 않도록 예방(한 번에 필요한 자원을 모두 락 획득)
- 보수적이라 자원 활용도가 떨어질 수 있음
- 탐지(Detection)
- 데드락이 발생하는지 모니터링 그래프(Wait-For Graph) 등으로 주기적으로 검사
- 데드락이 발견되면, 해당 트랜잭션 중 하나를 강제 종료(롤백)
- 복구(Recovery)
- 데드락 발견 후, 일부 트랜잭션을 중단(롤백)해 자원을 해제시키고, 나머지를 계속 진행
- 롤백된 트랜잭션은 재시도(애플리케이션 로직에서 재시도 처리 등)
요약
- ACID 특성
- 트랜잭션은 원자성, 일관성, 고립성, 지속성을 지켜야 한다.
- 동시성 제어
- Locking, MVCC 등을 활용해 여러 트랜잭션이 동시에 접근해도 데이터 충돌을 막는다.
- 적절한 락 전략, 락 레벨 설정, 락 지속 시간 관리가 중요하다.
- 격리 수준
- READ UNCOMMITTED → READ COMMITTED → REPEATABLE READ → SERIALIZABLE 순으로 강도가 높아짐.
- 격리 수준이 높을수록 이상현상이 줄지만, 성능 영향도 커진다.
- 데드락
- 교착 상태가 발생하면 일부 트랜잭션을 중단(롤백)시켜 회복한다.
- DBMS에서 자동으로 데드락 탐지 후 롤백 처리를 해주는 경우가 많으며, 트랜잭션 로직에서 재시도 로직을 넣기도 한다.