트랜잭션 상태와 스케줄
트랜잭션은 BEGIN부터 COMMIT 또는 ROLLBACK까지의 여정에서 여러 상태를 거칩니다. 마치 택배가 접수 → 집하 → 배송 중 → 배달 완료 또는 접수 → 집하 → 분실 → 반환의 경로를 밟듯이, 트랜잭션도 정해진 상태 전이 경로를 따릅니다. 그리고 여러 트랜잭션이 동시에 실행될 때, 그 연산들의 실행 순서를 스케줄(Schedule)이라 합니다. 스케줄이 안전한지 판단하는 기준이 직렬 가능성(Serializability)이고, 이것이 동시성 제어의 이론적 토대입니다.
이 절에서는 트랜잭션의 상태 전이를 상세히 살펴보고, 직렬 스케줄과 비직렬 스케줄의 차이, 충돌 직렬 가능성의 판단 방법인 선행 그래프, 그리고 회복 가능 스케줄까지 깊이 있게 다룹니다.
트랜잭션 상태
트랜잭션은 생성부터 종료까지 다섯 가지 상태를 거칩니다. 각 상태는 명확한 의미를 가지며, 상태 전이는 단방향입니다. 한번 커밋된 트랜잭션은 다시 활동 상태로 돌아갈 수 없고, 한번 철회된 트랜잭션도 같은 실행 시도 안에서는 다시 커밋될 수 없습니다. 재시도는 같은 트랜잭션을 되살리는 것이 아니라 새 트랜잭션을 시작하는 것으로 봅니다.
이 다섯 상태는 DBMS 교과서에서 쓰는 추상 모델입니다. 실제 제품 내부에는 로그 flush 대기, 복제 확인 대기, 잠금 대기, 클라이언트 응답 대기처럼 더 세분화된 내부 상태가 있을 수 있지만, 복구와 동시성 제어를 설명할 때는 이 모델이 기준이 됩니다.
상태 전이 다이어그램
각 상태의 의미
활동 (Active)트랜잭션이 시작되어 SQL 연산을 실행하고 있는 상태입니다. BEGIN TRANSACTION 또는 첫 번째 SQL 문이 실행된 순간부터 이 상태에 진입합니다. 이 상태에서는 SELECT, INSERT, UPDATE, DELETE 등의 연산이 수행됩니다. 변경된 데이터 페이지가 디스크에 먼저 기록될 수도 있지만, 커밋 전 변경은 Undo/Redo 로그와 복구 절차에 의해 최종 결과로 확정되지 않습니다.
부분 커밋 (Partially Committed)트랜잭션의 마지막 연산까지 실행이 완료되고 COMMIT 처리를 시작한 과도기적 상태입니다. 이론 모델에서는 커밋 요청과 실제 커밋 확정 사이를 구분하기 위해 사용합니다. 실제 DBMS에서는 로그 flush, 동기 커밋 설정, 복제 확인 여부에 따라 커밋 포인트의 세부 기준이 달라질 수 있습니다.
필요한 Redo/WAL 기록이 안정적인 저장소에 기록되면 커밋 상태로 전이됩니다. 이 지점이 일반적인 커밋 포인트(Commit Point)입니다. 반대로 커밋 확정 전에 오류가 발생하면 실패 상태로 전이되고 복구 또는 ROLLBACK 절차가 진행됩니다.
부분 커밋과 커밋이 구분되는 이유는 DBMS의 내부 복구 모델 때문입니다. 사용자가 COMMIT을 입력하면 DBMS는 필요한 로그를 안정화한 뒤 성공을 응답합니다. 다만 비동기 커밋이나 복제 설정을 쓰는 경우에는 “응답 시점”과 “장애 후 보존 범위”를 설정 기준으로 따로 확인해야 합니다.
커밋 (Committed)트랜잭션의 변경이 확정된 상태입니다. 필요한 로그가 안정화된 커밋이라면 시스템 장애 뒤에도 변경 사항은 복구 대상이 됩니다. DBMS가 재시작되면 Redo/WAL 로그를 읽어 커밋된 변경을 다시 적용할 수 있기 때문입니다.
실패 (Failed)트랜잭션 실행 중 오류가 발생한 상태입니다. 오류의 원인은 다양합니다.
* 제약 조건 위반 — UNIQUE 중복, FK 참조 무결성 위반
* 데이터 타입 오류 — 문자열을 숫자 컬럼에 넣으려 함
* 교착 상태 (Deadlock) — DBMS가 하나의 트랜잭션을 희생자로 선택
* 디스크 공간 부족 — 테이블스페이스 또는 Redo 로그 공간 부족
* 타임아웃 — 락 대기 시간 초과
* 사용자 취소 — 애플리케이션에서 명시적 ROLLBACK실패 상태에 진입한 트랜잭션은 정상 진행이 어렵고 ROLLBACK 또는 SAVEPOINT 기반 복구가 필요합니다. 단, SQL 한 문장의 오류가 항상 전체 트랜잭션 실패를 뜻하는 것은 아니며 PostgreSQL처럼 오류 후 트랜잭션이 실패 상태로 고정되는 DBMS도 있습니다.
철회 (Aborted)ROLLBACK이 완료되어 트랜잭션 시작 전 상태로 복원된 상태입니다. Undo 로그를 사용하여 모든 변경을 역순으로 되돌립니다. 철회 후에는 두 가지 선택이 가능합니다.
교착 상태로 인한 실패라면 재시도가 적절하고, 비즈니스 규칙 위반이라면 포기가 적절합니다.
상태 전이 규칙 요약
| 현재 상태 | 전이 조건 | 다음 상태 |
|---|---|---|
| 활동 | 마지막 연산 완료 + COMMIT 요청 | 부분 커밋 |
| 활동 | 트랜잭션을 중단해야 하는 오류 | 실패 |
| 부분 커밋 | 필요한 로그 안정화 성공 | 커밋 |
| 부분 커밋 | 커밋 확정 전 오류 | 실패 |
| 실패 | ROLLBACK 완료 | 철회 |
| 커밋 | — | 종료 (최종 상태) |
| 철회 | — | 종료 (최종 상태) |
중요한 것은 커밋과 철회가 최종 상태라는 점입니다. 커밋된 트랜잭션을 일반적인 ROLLBACK으로 철회할 수 없고, 철회된 트랜잭션을 다시 커밋할 수도 없습니다. 커밋 후 업무적으로 되돌려야 한다면 보상 트랜잭션을 새로 실행해야 합니다.
스케줄의 개념
여러 트랜잭션이 동시에 실행될 때, 각 트랜잭션의 연산들이 어떤 순서로 실행되는지를 나타낸 것이 스케줄(Schedule)입니다. 예를 들어 T1이 {R(A), W(A), R(B), W(B)}을 실행하고, T2가 {R(A), W(A)}를 실행한다면, 이 6개 연산의 실행 순서가 하나의 스케줄입니다.
스케줄에서 중요한 제약은 각 트랜잭션 내부의 연산 순서는 변경할 수 없다는 것입니다. T1이 R(A) 후에 W(A)를 실행하도록 정의되어 있다면, 어떤 스케줄에서도 T1의 R(A)는 반드시 T1의 W(A)보다 먼저 실행되어야 합니다. 하지만 T1의 연산들 사이에 T2의 연산이 끼어들 수 있습니다.
직렬 스케줄 (Serial Schedule)
직렬 스케줄은 트랜잭션들이 하나씩 순차적으로 실행되는 스케줄입니다. 한 트랜잭션이 완전히 끝나야 다음 트랜잭션이 시작됩니다.
n개의 트랜잭션이 있으면 n!개의 직렬 스케줄이 가능합니다. T1, T2 두 개라면 2! = 2개(T1→T2, T2→T1), T1, T2, T3 세 개라면 3! = 6개의 직렬 스케줄이 존재합니다.
직렬 스케줄의 특징은 트랜잭션 간 간섭이 없다는 것입니다. 각 트랜잭션이 개별적으로 일관성을 보존한다면 직렬 실행 결과도 일관됩니다. 하지만 동시성이 전혀 없어서 처리량이 낮고, 한 트랜잭션이 I/O를 기다리는 동안에도 다른 트랜잭션을 실행할 수 없습니다.
비직렬 스케줄 (Non-serial Schedule)
비직렬 스케줄은 여러 트랜잭션의 연산이 교차(인터리빙)되어 실행되는 스케줄입니다.
비직렬 스케줄의 장점은 높은 동시성입니다. 여러 트랜잭션이 자원을 효율적으로 공유하여 전체 처리량(throughput)이 크게 향상됩니다.
하지만 비직렬 스케줄은 연산의 교차 방식에 따라 정확하지 않은 결과를 낼 수 있습니다. 다음 예시를 보겠습니다.
이처럼 비직렬 스케줄은 교차 방식에 따라 올바른 결과를 낼 수도 있고, 잘못된 결과를 낼 수도 있습니다. 그래서 어떤 비직렬 스케줄이 안전한가?를 판단하는 기준이 필요합니다. 그것이 바로 직렬 가능성입니다.
손실 갱신 예시에서 W(A=90), W(A=80)처럼 절대값을 쓰는 표기는 각 트랜잭션이 예전에 읽은 값 100을 기준으로 계산한 결과를 뒤늦게 기록한다는 뜻입니다. 실제 SQL에서는 SET balance = balance - 10 같은 상대 업데이트인지, 애플리케이션이 읽은 값을 다시 쓰는 절대 업데이트인지에 따라 충돌 양상이 달라질 수 있습니다.
직렬 가능성 (Serializability)
비직렬 스케줄 S의 실행 결과가 어떤 직렬 스케줄의 결과와 동일하다면, 스케줄 S는 직렬 가능(serializable)하다고 합니다. 여기서 결과는 최종 데이터 값만 대충 같다는 뜻이 아니라, 각 트랜잭션이 읽은 값과 최종 쓰기 관계까지 같은 직렬 실행으로 설명될 수 있어야 한다는 뜻입니다. 직렬 가능한 스케줄은 동시성의 이점을 누리면서도 정확한 결과를 보장합니다.
직렬 가능성에는 두 가지 유형이 있습니다. 충돌 직렬 가능성(Conflict Serializability)과 뷰 직렬 가능성(View Serializability)입니다.
충돌 연산 (Conflicting Operations)
두 연산이 충돌한다는 것은 다음 세 조건을 모두 만족하는 경우입니다.
1. 서로 다른 트랜잭션에 속한다 (Ti ≠ Tj)
2. 같은 데이터 항목에 접근한다 (같은 A에 접근)
3. 둘 중 하나 이상이 쓰기(Write)이다
충돌하는 쌍:
* R_i(A) vs W_j(A) — 읽기-쓰기 충돌
* W_i(A) vs R_j(A) — 쓰기-읽기 충돌
* W_i(A) vs W_j(A) — 쓰기-쓰기 충돌
충돌하지 않는 쌍:
* R_i(A) vs R_j(A) — 읽기-읽기 (충돌 아님)
* R_i(A) vs W_j(B) — 다른 데이터 항목 (충돌 아님)
* W_i(A) vs W_i(A) — 같은 트랜잭션 (충돌 아님)충돌하는 연산의 순서를 바꾸면 결과가 달라질 수 있지만, 충돌하지 않는 인접 연산은 서로 바꿔도 결과가 같습니다. 단, 어떤 경우에도 같은 트랜잭션 내부의 연산 순서는 보존해야 합니다.
충돌 동등 (Conflict Equivalent)
두 스케줄 S1과 S2가 충돌 동등하다는 것은, S1에서 충돌하지 않는 인접 연산들을 차례로 바꿔서 S2를 만들 수 있다는 뜻입니다. 다시 말해, 충돌하는 연산의 상대적 순서가 두 스케줄에서 동일합니다.
충돌 직렬 가능성 (Conflict Serializability)
비직렬 스케줄 S가 어떤 직렬 스케줄 S'와 충돌 동등하면, S는 충돌 직렬 가능합니다.
충돌 직렬 가능성을 판단하는 실용적인 방법이 선행 그래프(Precedence Graph)입니다.
선행 그래프 (Precedence Graph)
선행 그래프는 트랜잭션 간의 충돌 관계를 방향 그래프로 표현한 것입니다.
1. 각 트랜잭션을 노드로 표현
2. Ti의 연산이 Tj의 충돌 연산보다 먼저 실행되면 Ti → Tj 간선 추가
3. 그래프에 사이클이 없으면 → 충돌 직렬 가능 ✓
4. 그래프에 사이클이 있으면 → 충돌 직렬 불가능 ✗구체적인 예시로 살펴보겠습니다.
선행 그래프의 실무적 의미
DBMS가 실시간으로 선행 그래프를 구성하여 모든 스케줄의 직렬 가능성을 검사하는 것은 비용이 큽니다. 대신 DBMS는 락, 타임스탬프, MVCC, SSI 같은 프로토콜로 허용할 실행 순서를 제한하거나, 위험한 동시 실행을 중단시키는 방식으로 직렬 가능성 또는 격리 수준별 보장을 제공합니다.
뷰 직렬 가능성 (View Serializability)
뷰 직렬 가능성은 충돌 직렬 가능성보다 더 넓은 개념입니다. 스케줄 S와 직렬 스케줄 S'에서 다음 세 조건이 모두 같으면 뷰 동등(view equivalent)하다고 합니다.
1. 동일한 초기 읽기: S에서 Ti가 데이터 Q의 초기값을 읽으면,
S'에서도 Ti가 Q의 초기값을 읽어야 함
2. 동일한 읽기 소스: S에서 Ti가 Tj가 쓴 Q 값을 읽으면,
S'에서도 Ti가 Tj가 쓴 Q 값을 읽어야 함
3. 동일한 최종 쓰기: S에서 Ti가 Q의 최종 값을 쓰면,
S'에서도 Ti가 Q의 최종 값을 써야 함뷰 직렬 가능한 스케줄 집합은 충돌 직렬 가능한 스케줄 집합을 포함합니다. 즉, 충돌 직렬 가능하면 반드시 뷰 직렬 가능하지만, 뷰 직렬 가능하다고 반드시 충돌 직렬 가능한 것은 아닙니다.
하지만 일반적인 뷰 직렬 가능성 검사는 NP-complete 문제로 실용적이지 않습니다. 그래서 실제 DBMS는 충돌 직렬 가능성을 쉽게 보장하는 2PL 계열, 또는 MVCC/SSI처럼 구현 가능한 프로토콜을 사용해 격리 수준별 보장을 제공합니다. 특히 스냅샷 격리(Snapshot Isolation)는 읽기 일관성을 강하게 제공하지만 항상 직렬 가능하다는 뜻은 아니며, write skew 같은 이상 현상을 막으려면 SERIALIZABLE/SSI나 명시적 잠금이 필요할 수 있습니다.
회복 가능 스케줄 (Recoverable Schedule)
직렬 가능성과는 별도로 스케줄의 회복 가능성도 중요합니다. 트랜잭션이 실패했을 때 안전하게 롤백할 수 있는 스케줄이어야 합니다.
회복 불가능한 스케줄의 문제
이 문제가 더티 읽기(Dirty Read)에 기반한 커밋입니다. T2가 T1의 아직 커밋되지 않은 데이터를 읽고 먼저 커밋하면, T1이 롤백되었을 때 T2도 롤백해야 하지만 이미 커밋되어 돌이킬 수 없습니다.
회복 가능 조건
스케줄이 회복 가능하려면: Ti가 Tj가 쓴 데이터를 읽었다면, Tj의 커밋이 Ti의 커밋보다 먼저 이루어져야 합니다.
비연쇄적 스케줄 (Cascadeless Schedule)
회복 가능 스케줄 중에서도, 커밋되지 않은 데이터를 아예 읽지 않는 스케줄을 비연쇄적 스케줄이라 합니다. 이는 연쇄적 롤백(Cascading Rollback)을 방지합니다.
비연쇄적 스케줄에서는 Ti가 Tj가 쓴 데이터를 읽으려면, Tj가 이미 커밋된 상태여야 합니다. 이렇게 하면 Tj가 롤백될 일이 없으므로 연쇄적 롤백이 발생하지 않습니다.
엄격한 스케줄 (Strict Schedule)
가장 엄격한 조건의 스케줄입니다. Ti가 쓴 데이터를 Tj가 읽거나 쓰려면, Ti가 이미 커밋 또는 롤백된 상태여야 합니다. 읽기뿐 아니라 쓰기도 제한합니다.
엄격한 스케줄은 복구가 쉽기 때문에 중요한 기준입니다. 다만 대부분의 DBMS가 모두 Strict 2PL만 쓰는 것은 아닙니다. SQL Server처럼 잠금 기반 격리가 강한 시스템도 있고, Oracle과 PostgreSQL처럼 MVCC를 중심으로 읽기와 쓰기를 분리하는 시스템도 있습니다.
회복 가능성의 강도는 보통 엄격한 스케줄 ⊂ 비연쇄적 스케줄 ⊂ 회복 가능 스케줄로 이해하면 됩니다. 엄격한 스케줄일수록 동시성 여지는 줄 수 있지만, 실패 시 되돌리기 쉽고 이미 커밋된 결과를 다시 물리는 상황을 피하기 쉽습니다.
DBMS에서의 스케줄 관리
실제 DBMS가 스케줄을 어떻게 관리하는지 정리합니다.
동시성 제어 프로토콜과 스케줄
| 프로토콜/기법 | 보장 경향 | 스케줄 관점 | 예시 |
|---|---|---|---|
| Basic 2PL | 충돌 직렬 가능 | 교착/회복 조건은 별도 | 이론 모델 |
| Strict 2PL | 충돌 직렬 가능 + 엄격성 | 복구가 쉬움 | 잠금 기반 격리의 대표 모델 |
| MVCC | 읽기/쓰기 분리, 스냅샷 제공 | 격리 수준별 보장 차이 | PostgreSQL, Oracle, InnoDB |
| Serializable SI/SSI | 직렬화 이상을 감지하고 중단 | 재시도 필요 가능 | PostgreSQL SERIALIZABLE |
| DBMS별 혼합 구현 | 락 + MVCC + 로그 복구 조합 | 단일 이론 모델과 다를 수 있음 | 상용 DBMS 전반 |
이 표는 구현 원리를 단순화한 것입니다. 같은 MVCC라도 Oracle의 read consistency, PostgreSQL의 SSI, InnoDB의 next-key lock 조합은 서로 다르게 동작합니다. 따라서 "MVCC라서 직렬 가능" 또는 "락 기반이라서 항상 엄격"처럼 단정하면 안 됩니다.
격리 수준과 스케줄의 관계
READ UNCOMMITTED:
→ 더티 읽기를 허용할 수 있음 → 회복 가능성 위험 증가
→ 실무에서 거의 사용하지 않음
READ COMMITTED:
→ 커밋된 데이터만 읽음 → 더티 읽기 방지
→ Oracle, PostgreSQL 기본값
REPEATABLE READ:
→ 반복 읽기 안정성 강화
→ MySQL InnoDB 기본값
SERIALIZABLE:
→ 직렬 실행과 동등한 결과를 목표로 함
→ 대기, 교착, serialization failure로 재시도가 필요할 수 있음직렬 가능성 검증 실습
선행 그래프를 직접 그려서 충돌 직렬 가능성을 판단하는 연습을 해봅시다.
세 개 이상의 트랜잭션에서의 선행 그래프
트랜잭션이 세 개 이상일 때의 선행 그래프도 같은 원리로 분석합니다.
요약 정리
| 개념 | 설명 |
|---|---|
| 트랜잭션 상태 | 활동 → 부분 커밋 → 커밋 또는 실패 → 철회 |
| 직렬 스케줄 | 트랜잭션이 순차 실행, 간섭은 없지만 동시성 낮음 |
| 비직렬 스케줄 | 연산이 교차 실행, 빠르지만 안전성 검증 필요 |
| 충돌 직렬 가능 | 선행 그래프에 사이클이 없으면 충돌 직렬 가능 |
| 뷰 직렬 가능 | 더 넓은 직렬 가능성, 일반 검증은 NP-complete |
| 회복 가능 스케줄 | 읽은 데이터의 작성자가 먼저 커밋해야 함 |
| 비연쇄적 스케줄 | 커밋된 데이터만 읽어 연쇄 롤백 방지 |
| 엄격한 스케줄 | 미완료 트랜잭션이 쓴 같은 데이터 항목의 읽기·쓰기 제한 |
트랜잭션의 상태 전이는 DBMS가 장애 복구를 할 때 핵심이 됩니다. 미커밋 변경은 Undo로 정리하고, 커밋된 변경 중 데이터 파일 반영이 덜 끝난 것은 Redo로 재적용합니다. 스케줄의 직렬 가능성은 동시성 제어의 정확성을 판단하는 이론적 기준이며, 선행 그래프는 그 판단을 설명하는 대표 도구입니다.
다음 절에서는 실무에서 트랜잭션을 어떻게 관리하는지 다루겠습니다.