database

Database Transaction

Database에서 트랜잭션(Transaction)이란 DB의 상태를 변화시키기 위한 일련의 작업 단위로 데이터베이스에서 여러 쿼리를 하나의 작업 단위로 묶어 처리하는 개념입니다. 이를 통해 원자성(Atomicity), 일관성(Consistency), 독립성(Isolation), 지속성(Durability)이라는 네 가지 핵심 특성을 확보할 수 있는데, 이를 흔히 ACID라고 부릅니다. 먼저 원자성(Atomicity)은 트랜잭션에 포함된 모든 쿼리가 전부 반영되거나 하나도 반영되지 않는 특성을 의미합니다. 일관성(Consistency)은 트랜잭션 실행 전후로 데이터베이스의 결과가 일관성이 있어야 함을 의미하고, 독립성(Isolation)은 서로 다른 트랜잭션이 동시에 실행되더라도 각자의 연산이 간섭받지 않도록 보호되는 것을 의미합니다. 마지막으로 지속성(Durability)은 트랜잭션이 성공적으로 마무리되면 그 결과가 영구적으로 유지된다는 의미입니다.


이러한 트랜잭션을 다룰 때 대표적으로 사용하는 명령으로는 Commit과 Rollback이 있습니다. Commit은 트랜잭션 내 작업이 정상적으로 끝났을 때 변경 사항을 데이터베이스에 최종 반영하는 명령이며, Rollback은 트랜잭션 수행 도중 문제가 생겼을 때 작업 이전 상태로 되돌리는 역할을 합니다. 이를 통해 예상치 못한 오류나 충돌이 발생하더라도, 데이터 무결성을 지키고 안정적인 데이터베이스 운영이 가능해집니다.

트랜잭션 격리 수준(Isolation Level)이란?

데이터베이스에서 트랜잭션은 여러 개의 쿼리를 논리적으로 하나의 작업 단위로 묶어 처리합니다. 하지만 여러 트랜잭션이 동시다발적으로 실행되는 경우, 데이터를 어떻게 격리해야 안전하게 관리할 수 있을까요? 바로 이때 중요한 것이 트랜잭션 격리 수준(Isolation Level)입니다. 격리 수준은 트랜잭션 간 상호 간섭을 어떤 정도까지 허용할지를 결정하는 기준이며, 각 수준마다 데이터 무결성과 동시성(Concurrency) 간의 트레이드오프가 존재합니다.


트랜잭션들끼리 얼마나 고립되어있는지(잠금 수준)을 나타내는 것으로, 격리 수준은 크게 4가지로 표현됩니다. READ UNCOMMITTED (트렌젝션 레벨 0), READ COMMITTED (트렌젝션 레벨 1), REPEATABLE READ (트렌젝션 레벨 2), SERIALIZABLE (트렌젝션 레벨 3) 트랜잭션 레벨이 높아질 수록 고립도가 높아지고 성능이 떨어집니다.


1. df (Read Uncommitted, 커밋되지 않은 읽기)

Read Uncommitted는 “커밋되지 않은 데이터를 읽을 수 있게 하는” 가장 낮은 격리 수준을 의미합니다. 다른 트랜잭션에서 아직 커밋되지 않은, 즉 확정되지 않은 데이터를 읽을 수 있습니다. 이 말은 트랜잭션이 진행 도중에 롤백될 수도 있으므로, 일시적으로 볼 수 있었던 데이터가 실제 DB에 반영되지 않을 가능성이 있습니다. 아직 커밋되지 않은 데이터를 다른 트랜잭션이 읽을 수 있어서, “Dirty Read”가 발생합니다. 예를 들어, A 트랜잭션이 어떤 데이터를 수정했지만 커밋하기 전에 B 트랜잭션이 그 데이터를 읽어버린 뒤, A 트랜잭션이 롤백한다면 B 트랜잭션은 유효하지 않은 데이터를 읽은 셈이 됩니다.


Dirty Read가 발생할 수 있을 만큼 보안이나 무결성 면에서 가장 위험하지만, 동시에 가장 높은 동시성(속도)를 확보할 수 있는 수준입니다.


2. Committed Read (커밋된 읽기)

Committed Read는 “커밋된 데이터에만 접근하는” 격리 수준으로, Read Uncommitted보다 한 단계 더 엄격합니다. 다른 트랜잭션에서 변경한 값이라도, 이미 커밋된 데이터만 읽을 수 있습니다. 따라서 Dirty Read 문제는 해결됩니다. A 트랜잭션이 같은 SELECT 문을 두 번 실행했을 때, 그 사이에 B 트랜잭션이 데이터를 변경하고 커밋하면 A 트랜잭션이 얻는 결과가 달라질 수 있습니다. 이를 Non-Repeatable Read라고 부릅니다.


Dirty Read는 방지하지만, 동일한 SELECT 쿼리를 반복 실행했을 때 결과가 일관되지 않을 수 있어 반복된 읽기(Repeatable Read)가 보장되지 않습니다.


3. Repeatable Read (반복 가능한 읽기)

Repeatable Read는 “동일한 트랜잭션 내에서는 같은 데이터를 읽으면 항상 같은 결과가 반환되도록” 보장하는 격리 수준입니다. MySQL InnoDB 등 일부 DB 엔진에서는 MVCC(Multi-Version Concurrency Control)로 구현합니다. Undo 로그를 활용하여, 트랜잭션이 시작된 시점의 스냅샷을 계속 참조함으로써 동일한 SELECT 쿼리를 실행하면 같은 결과를 얻도록 만듭니다. 이 방식으로 Non-Repeatable Read 문제를 해결합니다. Phantom Read는 “하나의 트랜잭션 내에서 동일한 조건의 SELECT 쿼리를 실행했을 때, 레코드 개수가 달라지는 현상”을 말합니다. 예를 들어 첫 번째 SELECT에서 10개의 레코드를 읽었는데, 다른 트랜잭션에서 INSERT를 커밋한 뒤 두 번째 SELECT에서는 11개 레코드가 나오는 상황을 의미합니다. 일반적으로 Repeatable Read 수준에서는 Phantom Read가 발생할 수 있으나, MySQL의 InnoDB는 select ... for update 시 Next-Key Lock 같은 배타락을 활용하여 Phantom Read를 방지합니다. OracleDB 등 MVCC 구현이 다른 DB의 경우엔 Phantom Read가 여전히 발생할 수 있으며, 이를 해결하기 위해 Locking 방식으로 접근하기도 합니다.


Non-Repeatable Read까지 방지하지만, 일부 DB에서는 Phantom Read가 발생할 수 있습니다. 다만 MySQL InnoDB 등 특정 엔진에서는 Phantom Read도 대부분 방지합니다.


4. Serializable (직렬화 가능)

Serializable은 “모든 트랜잭션을 순차적으로 실행하는 것과 동일하게 보이도록” 하는 최상위 격리 수준입니다. 트랜잭션 간에 병행 실행이 거의 제한되어, 모든 부정합 문제(Dirty Read, Non-Repeatable Read, Phantom Read)를 완벽히 방지할 수 있습니다. 처리 과정을 직렬(순차)로 보장하기 때문에, 동시성 측면에서 성능 저하가 클 수 있습니다. 서로 트랜잭션이 동시에 접속하려고 하면 순차 처리 때문에 대기가 발생하거나, 동시에 Lock을 요청하여 데드락 상황이 생길 수 있습니다.


가장 안전하지만 가장 느린 수준이며, 실제 운영 환경에서는 성능 저하를 유의해야 합니다.


대부분의 데이터베이스에서는 기본적으로 Repeatable Read 또는 Committed Read를 채택해, 무결성과 성능 간의 합리적인 타협점을 도출합니다. 프로젝트 특성 및 데이터 중요도, 동시성 요구사항 등을 고려하여 적합한 격리 수준을 선택하는 것이 중요합니다.