개발을 하면서 '트랜잭션'이라는 워딩에 대하여 한 번쯤은 들어봤을 것이다. 나도 몇 차례의 프로젝트를 진행하고, DB를 다루면서 항상 접했던 단어이지만 명확하게 어떤 의미인지는 잘 알지 못했다. 특히, spring-boot를 사용할 때 @Transactional 이라는 어노테이션을 꼭 사용해야 한다고 보아 곧잘 사용하긴 했는데, 이것이 어떤 역할을 하고 있는지는 잘 알지 못했다.
이해가 선행되지 않다보니, 트랜잭션을 사용해도 어떨 때는 DB에 commit이 잘 되었다가 어떨 때는 값이 전혀 들어가지 않는 등 이유를 알 수 없는 상황을 많이 겪게 되었던 경험이 있다. 유레카 과정을 진행하며 때마침 transaction에 대해 학습할 수 있는 기회가 주어졌기 때문에, 학습한 내용을 간략하게 정리해 보았다.
트랜잭션(Transaction)이란?
트랜잭션은 데이터베이스에서 하나의 논리적 작업 단위를 말한다. 보통 여러 개의 SQL문을 하나로 묶어서 처리하는데, 이때 오류가 발생하면 이미 실행한 것들도 전부 Rollback을 하게 된다. 오류 이전에 수행이 된 SQL문이라도 실제로 DB에 반영되어 저장되지 않고, 아무것도 실행하지 않은 것처럼 돌아가는 것이다.
즉, 트랜잭션은 All or Nothing, 모두 성공하거나 모두 실패하는 형태를 가진다.

트랜잭션의 4가지 성질 (ACID)
트랜잭션은 4가지 성질을 가지고 있다.
1. 원자성 (Atomicity)
앞서 언급한 All or Nothing이 바로 원자성이다.
트랜잭션은 모든 연산이 완전히 수행되거나, 전혀 수행되지 않는 성질을 가지고 있다. 중간에 모종의 이유로 실패가 할생할 경우 Rollback을 통해 모든 연산을 취소하고, 오류가 없을 경우 commit을 통해 모든 연산을 완전히 수행하게 된다.

mybatis, jpa 등을 활용할 때에도 commit을 명시해주어야 transaction이 종료되었다고 판단하여 DB에 값을 저장한다.
2. 일관성 (Consistency)
일관성은 트랜잭션 실행 전후에 데이터베이스가 일관된 상태를 유지해야 한다는 것이다.
말이 어렵다고 느껴질 수 있으나, 간단하게 말하면 다음과 같다.
A가 실행되는 동안 데이터베이스가 변경이 되더라도, A는 트랜잭션을 시작했을 때 가져온 데이터를 기반으로 작업을 수행한다.
만약, 일관성을 유지하지 않으면 같은 작업 단위를 수행하는데 사용되는 데이터가 계속해서 바뀌는 불상사가 벌어질 수 있다. 예를 들어, 결제를 하는 A 트랜잭션을 진행 중일 때, a 데이터베이스의 값이 5000 -> 10000으로 변경되었다고 가정해보자. 사용자는 결제 금액을 5000으로 알고 결제를 진행하고 있었는데, 막상 결제가 완료되고 보니 10000원이 결제되어버리는 사태가 발생할 수 있는 것이다.
따라서, 트랜잭션의 일관성은 사용자에게 일관된 데이터를 제공할 수 있다.
만약 무결성 제약조건이 위배 되었을 경우 rollback을 하게 된다.
3. 고립성 (Isolation)
고립성은 둘 이상의 트랜잭션이 동시에 실행되고 있을 때, 트랜잭션 간에 서로 영향을 주고받지 않는 것이다. 고립성이 지켜지면 여러 트랜잭션이 동시에 실행되어도 순차 실행한 것과 같은 결과를 보이게 된다. 이는 고립성 격리 수준(Isolation Level)로 조정이 가능하다.
4. 지속성 (Durability)
지속성은 트랜잭션이 성공적으로 완료되었을 경우, commit된 트랜잭션의 결과가 영구적으로 반영되어야 한다는 것이다. 영구적이라 함은 시스템에 장애가 발생하더라도 데이터는 유지되어야 함을 의미하며, 로그 파일을 통한 회복이 가능해야 한다.
트랜잭션 격리 수준 (Isolation Level)
격리 수준이란?
- 동시에 실행되는 트랜잭션들이 얼마나 격리되는지 정의
- 데이터 일관성 vs 성능의 트레이드오프
| 격리 수준 | Dirty Read | Non-Repeatable Read | Phantom Read | 사용 |
| READ UNCOMMITTED | ⭕ | ⭕ | ⭕ | ❌ |
| READ COMMITTED | ❌ | ⭕ | ⭕ | ✅ Oracle |
| REPEATABLE READ | ❌ | ❌ | ⭕* | ✅ MySQL |
| SERIALIZABLE | ❌ | ❌ | ❌ | ❌ |
1. READ UNCOMMITTED (레벨 0)
레벨 0은 커밋이 되지 않은 데이터도 읽어온다.
성능적인 면에서는 가장 빠른 속도를 보여주지만, 다른 트랜잭션에서 수행한 커밋이 되지 않은 데이터도 모두 보여주면서 일관성을 유지하지 않게 된다. 즉, Dirty Read가 발생할 수 있다.

2. READ COMMITTED (레벨 1)
레벨 1은 커밋된 데이터만 읽어온다.
따라서 레벨 0에서 문제가 되었던 Dirty Read 방지가 가능하다. 이로 인해 일관성이 부분적으로 유지가 된다고 볼 수 있으나, Non-Repeatable Read 문제가 발생하여 일관성이 깨질 수 있다. Non-Repeatable Read는 반복 불가능한 읽기 문제로, 예를 들어 트랜잭션 1에서 a=10을 읽고, 트랜잭션 2에서 a=20으로 수정을 했다고 가정했을 때, 트랜잭션 1에서 a를 다시 읽으니 20이 되는 문제가 발생하는 것이다.

즉, 한 트랜잭션 내에서도 읽을 때마다 데이터가 달라져 일관성이 깨질 수 있다.
Oracle, PostgreSQL을 포함한 대부분의 RDBMS는 READ COMMITED를 기본값으로 사용하고 있다.
3. REPEATABLE READ (레벨 2)
레벨 2는 고립성이 강해진다. 트랜잭션이 시작된 시점의 데이터를 고정(MVCC)하여 트랜잭션이 끝날 때까지 다른 트랜잭션이 수정한 데이터를 가져오지 않는다. 즉, Non-Repeatable Read를 방지한다.

이렇듯 일관성이 대부분 보장되지만, 값이 변경(update)되는 경우가 아니라 새로운 값이 추가(insert) / 삭제(delete) 되는 경우에는 일관성을 보장하지 못한다. 이것을 Phantom Read라고 한다. 존재하지 않던 데이터가 트랜잭션 중간에 유령처럼 갑자기 등장하는 것이다.

MySQL은 REPEATABLE READ를 기본값으로 갖는다.
4. SERIALIZABLE (레벨 3)
레벨 3은 고립성을 완벽하게 보장한다. 트랜잭션을 마지 순차적으로 하나씩 실행하는 것과 동일한 결과를 보장한다. 테이블 전체에 락을 거는 등의 방식으로 수행된다.
일관성 또한 완벽하게 보장된다. 트랜잭션 자체가 순차적으로 실행되는 것 같은 결과를 보장하기 때문에, 트랜잭션이 동시 실행될 가능성이 전혀 없고, 따라서 일관성이 침해될 여지가 전혀 없다. 다만, 한 번에 하나의 트랜잭션을 수행하는 것이기 때문에 동시 처리가 거의 불가능해져 성능 면에서는 최악이 된다.

즉, SERIALIZABLE은 모든 이상 현상을 방지하지만 동시에 성능도 매우 악화되는 격리 레벨이다.
'유레카' 카테고리의 다른 글
| 구글 제미나이(gemini)로 자기소개 페이지 만들기 (0) | 2025.10.28 |
|---|---|
| [유플러스 유레카] 미니프로젝트 (3 DAYS) - 스터디카페 관리 시스템 (0) | 2025.10.26 |
| [LG 유플러스 유레카 #2] 알고리즘 - 순열, 조합 (0) | 2025.09.21 |
| [LG 유플러스 유레카 #1] 9월 1주차, 버퍼, BufferedReader, InputStreamReader (0) | 2025.09.09 |