← 전체 글

[CS] 트랜잭션 ACID · 격리수준

1. 트랜잭션은 왜 필요할까?

데이터베이스에서는 하나의 작업을 처리하기 위해 여러 SQL을 실행해야 하는 경우가 많다.

대표적인 예로 계좌 이체가 있다.

A 계좌에서 B 계좌로 10만 원을 이체한다고 생각해보자.

UPDATE accounts
SET balance = balance - 100000
WHERE id = 'A';

UPDATE accounts
SET balance = balance + 100000
WHERE id = 'B';

정상적으로 처리된다면,

A 계좌에서 10만 원 차감
        ↓
 B 계좌에 10만 원 추가

되어야 한다.

그런데 첫 번째 SQL은 성공했지만 두 번째 SQL을 실행하는 도중 서버에 문제가 발생한다면 어떻게 될까?

A 계좌 -10만원 O

      ↓

   서버 장애

      ↓

B 계좌 +10만원 X

A의 돈은 사라졌지만 B에게는 돈이 들어오지 않는 문제가 발생한다.

따라서 두 작업을 각각 별개의 작업으로 처리해서는 안 된다.

A 계좌 출금
	+
B 계좌 입금

= 하나의 작업

처럼 묶어서 처리해야 한다.

이때 사용하는 것이 **Transaction(트랜잭션)**이다.


2. 트랜잭션이란?

트랜잭션은 데이터베이스에서 수행되는 하나의 논리적인 작업 단위이다.

하나의 트랜잭션은 여러 SQL로 구성될 수 있으며 기본적인 원칙은:

모든 작업이 성공하거나, 모든 작업이 실패해야 한다.

즉, All or Nothing이다.

BEGIN;

UPDATE accounts
SET balance = balance - 100000
WHERE id = 'A';

UPDATE accounts
SET balance = balance + 100000
WHERE id = 'B';

COMMIT;

모든 작업이 정상적으로 실행되면 COMMIT을 수행한다.

COMMIT
→ 트랜잭션의 변경사항 확정

반대로 문제가 발생하면 ROLLBACK을 수행한다.

ROLLBACK
→ 트랜잭션에서 수행한 변경사항 취소

예를 들어,

   BEGIN

A 계좌 -10만원
     ↓
B 계좌 +10만원
     ↓
  COMMIT

→ 모두 반영

도중 문제가 발생하면,

   BEGIN

A 계좌 -10만원
     ↓
  오류 발생
     ↓
  ROLLBACK

→ A 계좌 변경도 취소

3. ACID란?

트랜잭션은 데이터의 신뢰성을 보장하기 위해 일반적으로 ACID라고 불리는 네 가지 성질을 가진다.

A → Atomicity
C → Consistency
I → Isolation
D → Durability

3.1 Atomicity - 원자성

원자성은 트랜잭션에 포함된 작업이:

전부 실행되거나 전부 실행되지 않아야 한다

는 성질이다.

예를 들어 주문을 생성한다고 생각해보자.

주문 생성
   ↓
주문 상품 저장
   ↓
재고 감소

이 세 작업이 하나의 트랜잭션이라면

주문 생성       O
주문 상품 저장   O
재고 감소       O

전부 성공하거나

주문 생성       X
주문 상품 저장   X
재고 감소       X

전부 취소되어야 한다.

중간까지만 반영되면

주문은 생성됐는데
재고는 줄지 않음

같은 데이터 불일치가 발생할 수 있기 때문이다.

DBMS는 Undo Log 등의 메커니즘을 통해 실패한 작업을 되돌릴 수 있다.


3.2 Consistency - 일관성

일관성은:

트랜잭션 실행 전과 실행 후에도 데이터베이스의 규칙이 유지되어야 한다

는 성질이다.

예를 들어 계좌의 잔액은 음수가 될 수 없다는 규칙이 있다고 하자.

balance >= 0

잔액이 5만 원인데 10만 원을 출금해서

balance = -50000

이 된다면 데이터베이스의 규칙이 깨진 것이다.

또한

Primary Key
Foreign Key
Unique
Not Null
Check

같은 무결성 제약조건이나 애플리케이션의 비즈니스 규칙 역시 일관성을 유지하는 데 사용된다.

즉,

Consistency

트랜잭션 전
정상적인 DB 상태

     ↓

트랜잭션 실행

     ↓

트랜잭션 후
정상적인 DB 상태

를 유지해야 한다.


3.3 Isolation - 격리성

여러 사용자가 동시에 요청을 보내면 데이터베이스에서도 여러 트랜잭션이 동시에 실행된다.

Transaction A ─┐
               ├→ Database
Transaction B ─┘

이때 한 트랜잭션의 작업 때문에 다른 트랜잭션이 예상하지 못한 데이터를 읽게 된다면 문제가 발생할 수 있다.

격리성은

동시에 실행되는 트랜잭션들이 서로에게 잘못된 영향을 주지 않도록 보장하는 성질

이다.

이론적으로는 각 트랜잭션이 마치 혼자 실행되는 것과 같은 결과를 만드는 것이 가장 안전하다.

하지만 모든 트랜잭션을 실제로 하나씩 실행한다면

T1 실행
 ↓
T1 완료
 ↓
T2 실행
 ↓
T2 완료

동시성이 크게 떨어져 성능이 나빠질 수 있다.

그래서 DBMS는 **Isolation Level(격리 수준)**을 통해 격리성과 동시성 사이의 균형을 조절한다.


3.4 Durability - 지속성

지속성은

한번 COMMIT된 데이터는 시스템에 문제가 발생해도 유지되어야 한다

는 성질이다.

Transaction
    ↓
 COMMIT
    ↓
서버 장애
    ↓
재시작
    ↓
COMMIT된 데이터 유지

DBMS는 WAL(Write-Ahead Logging), Redo Log 등의 로그를 이용해 장애가 발생했을 때 데이터를 복구할 수 있다.

즉,

COMMIT 완료
→ 변경사항이 영구적으로 보장

되는 것이 지속성이다.


4. 왜 격리 수준이 필요할까?

가장 안전한 방법은 모든 트랜잭션을 완전히 격리하는 것이다.

하지만 격리를 강하게 할수록 다른 트랜잭션을 기다리거나 충돌을 검사해야 하는 경우가 늘어날 수 있다.

격리 수준 낮음

동시성 ↑
성능 ↑
데이터 이상 현상 가능성 ↑

반대로

격리 수준 높음

동시성 ↓
충돌/대기 가능성 ↑
데이터 안정성 ↑

이러한 균형을 조절하기 위해 SQL 표준에서는 대표적으로 네 가지 격리 수준을 정의한다.

READ UNCOMMITTED
        ↓
READ COMMITTED
        ↓
REPEATABLE READ
        ↓
SERIALIZABLE

아래로 갈수록 격리 수준이 높아진다.


5. 트랜잭션에서 발생할 수 있는 이상 현상

격리 수준을 이해하려면 먼저 동시에 트랜잭션을 실행할 때 어떤 문제가 발생할 수 있는지 알아야 한다.

대표적으로

Dirty Read
Non-Repeatable Read
Phantom Read

가 있다.

추가로 실무에서 자주 언급되는 Lost Update도 있다.


5.1 Dirty Read

Dirty Read는 다른 트랜잭션이 아직 COMMIT하지 않은 데이터를 읽는 현상이다.

예를 들어

초기 balance = 1000

Transaction A가

A: balance = 500으로 변경

아직 COMMIT하지 않음

상태라고 하자.

이때 Transaction B가

B: balance 조회

→ 500

을 읽어버린다.

그런데 이후 A가

ROLLBACK

한다면 실제 데이터는 다시

balance = 1000

이 된다.

결국 B는 실제로 확정된 적이 없는 500이라는 값을 기반으로 작업한 것이다.

T1

1000 → 500
      아직 COMMIT X

          ↓

T2
500 읽음

          ↓

T1 ROLLBACK

          ↓

실제 값 = 1000

이를 Dirty Read라고 한다.


5.2 Non-Repeatable Read

Non-Repeatable Read는

하나의 트랜잭션 안에서 같은 데이터를 두 번 읽었는데 값이 달라지는 현상

이다.

예를 들어

balance = 1000

일 때

T1
SELECT balance
→ 1000

그 사이 T2가

T2
UPDATE balance = 500
COMMIT

한다.

그리고 T1이 다시 조회한다.

T1
SELECT balance
→ 500

그러면 같은 트랜잭션 안에서

첫 번째 조회 → 1000

두 번째 조회 → 500

처럼 결과가 바뀐다.

T1            T2

SELECT
→ 1000

              UPDATE
              → 500

              COMMIT

SELECT
→ 500

이를 Non-Repeatable Read라고 한다.


5.3 Phantom Read

Phantom Read는 하나의 트랜잭션에서 같은 범위 조건으로 조회했는데 없던 행이 나타나거나 기존 행이 사라지는 현상이다.

예를 들어

SELECT *
FROM products
WHERE price >= 10000;

을 실행했더니 10개의 상품이 조회됐다고 하자.

그 사이 다른 트랜잭션이

INSERT INTO products(name, price)
VALUES ('New Product', 20000);

COMMIT;

을 수행한다.

첫 번째 트랜잭션에서 같은 조건으로 다시 조회했더니

첫 번째 조회 → 10개

두 번째 조회 → 11개

가 될 수 있다.

T1                         T2

price >= 10000 조회
→ 10개

                           새로운 상품 INSERT
                           COMMIT

다시 조회
→ 11개

마치 없던 데이터가 갑자기 나타난 것처럼 보이기 때문에 Phantom Read라고 한다.

Non-Repeatable Read와 차이는

Non-Repeatable Read
→ 기존 행의 값이 변경됨

Phantom Read
→ 조회되는 행 자체가 추가되거나 사라짐

이다.


5.4 Lost Update

Lost Update는 두 트랜잭션이 같은 데이터를 동시에 수정하면서 한쪽의 변경사항이 사라지는 현상이다.

초기 값이

balance = 1000

이라고 하자.

두 트랜잭션이 동시에 읽는다.

T1 → 1000 조회
T2 → 1000 조회

T1은 100을 추가한다.

1000 + 100
→ 1100

T2는 200을 추가한다.

1000 + 200
→ 1200

그리고 각각 저장하면

T1 UPDATE → 1100

T2 UPDATE → 1200

최종 값은

1200

이 된다.

하지만 실제로 두 변경사항이 모두 적용됐다면

1000 + 100 + 200
= 1300

이 되어야 한다.

즉 T1의 수정이 T2에게 덮어씌워져 사라진 것이다.

이를 Lost Update라고 한다.

이런 문제는 상황에 따라

SELECT ...
FOR UPDATE;

와 같은 명시적 Lock이나,

UPDATE accounts
SET balance = balance + 100
WHERE id = 1;

처럼 DB에서 직접 원자적으로 값을 변경하는 방법 등을 사용할 수 있다.


6. Isolation Level

6.1 READ UNCOMMITTED

가장 낮은 격리 수준이다.

다른 트랜잭션이 아직 COMMIT하지 않은 데이터도 읽을 수 있다.

T1
UPDATE
아직 COMMIT X

       ↓

T2
변경된 값 조회 가능

따라서 Dirty Read가 발생할 수 있다.

READ UNCOMMITTED

Dirty Read             O
Non-Repeatable Read    O
Phantom Read           O

데이터의 신뢰성이 낮기 때문에 일반적인 서비스에서는 거의 사용하지 않는다.


6.2 READ COMMITTED

COMMIT된 데이터만 읽을 수 있는 격리 수준이다.

즉, Dirty Read를 방지할 수 있다.

하지만 하나의 트랜잭션이 실행되는 동안 다른 트랜잭션이 데이터를 수정하고 COMMIT하면 다음 조회에서 변경된 값이 보일 수 있다.

따라서

Non-Repeatable Read
Phantom Read

는 발생할 수 있다.

READ COMMITTED

Dirty Read             X
Non-Repeatable Read    O
Phantom Read           O

PostgreSQL과 Oracle 등에서 기본 격리 수준으로 사용된다.


6.3 REPEATABLE READ

REPEATABLE READ는 같은 트랜잭션 안에서 이미 읽은 데이터가 반복해서 동일하게 보이도록 하는 격리 수준이다.

따라서

Dirty Read
→ 방지

Non-Repeatable Read
→ 방지

한다.

SQL 표준 기준으로는 Phantom Read가 발생할 수 있다.

REPEATABLE READ

Dirty Read             X
Non-Repeatable Read    X
Phantom Read           O

다만 실제 DBMS의 구현은 SQL 표준과 완전히 동일하지 않을 수 있다.

예를 들어 PostgreSQL의 REPEATABLE READ는 MVCC 기반 Snapshot을 사용하기 때문에 일반적인 Phantom Read도 발생하지 않는다.

MySQL InnoDB 역시 MVCC와 Lock을 이용해 SQL 표준보다 강한 동작을 제공하는 경우가 있다.

따라서 격리 수준을 사용할 때는 사용하는 DBMS의 실제 구현을 함께 확인해야 한다.


6.4 SERIALIZABLE

가장 높은 격리 수준이다.

동시에 실행되는 트랜잭션의 결과가 마치

T1 전체 실행
↓
T2 전체 실행

또는

T2 전체 실행
↓
T1 전체 실행

한 것과 같은 결과가 나오도록 보장한다.

따라서 대표적인 이상 현상을 가장 강하게 방지한다.

SERIALIZABLE

Dirty Read             X
Non-Repeatable Read    X
Phantom Read           X

다만 중요한 것은

SERIALIZABLE이 모든 트랜잭션을 실제로 하나씩 실행한다는 뜻은 아니다.

가능하면 동시에 실행하되 그 결과가 어떤 직렬 실행과 동일하도록 보장하는 것이다.

충돌을 안전하게 처리할 수 없는 경우 DBMS는 한 트랜잭션을 실패시키고 애플리케이션에 재시도를 요구할 수도 있다.

격리 수준이 높아질수록 이러한 충돌이나 대기 비용이 증가할 수 있기 때문에 무조건 높은 격리 수준을 사용하는 것이 정답은 아니다.


7. 격리 수준 정리

SQL 표준을 기준으로 대표적인 이상 현상을 정리하면 다음과 같다.

격리 수준Dirty ReadNon-Repeatable ReadPhantom Read
READ UNCOMMITTEDOOO
READ COMMITTEDXOO
REPEATABLE READXXO
SERIALIZABLEXXX

외우기보다는 단계적으로 이해하는 것이 쉽다.

READ UNCOMMITTED
→ 아무것도 제대로 막지 않음

READ COMMITTED
→ COMMIT된 데이터만 읽음
→ Dirty Read 방지

REPEATABLE READ
→ 같은 데이터를 반복해서 읽어도 동일
→ Non-Repeatable Read 방지

SERIALIZABLE
→ 직렬 실행과 동일한 결과 보장
→ 가장 강한 격리

단, 이 표는 SQL 표준에서 정의한 최소 기준이고 실제 동작은 PostgreSQL, MySQL 등의 DBMS 구현에 따라 달라질 수 있다.


8. Serializable은 정말 하나씩 실행하는 것일까?

SERIALIZABLE이라는 이름 때문에

Transaction 1 끝날 때까지
Transaction 2는 무조건 기다린다

라고 생각하기 쉽다.

하지만 Serial Execution과 Serializable은 다른 개념이다.

Serial Execution

트랜잭션을 실제로 하나씩 실행한다.

T1
READ
WRITE
COMMIT

      ↓

T2
READ
WRITE
COMMIT

Serializable

트랜잭션을 동시에 실행할 수도 있지만 최종 결과가 어떤 Serial Execution과 동일하다.

T1 ─────────┐
            ├→ 동시에 실행
T2 ─────────┘

결과

=

T1 → T2

또는

T2 → T1

순서로 실행한 결과

즉 Serializable의 핵심은

동시에 실행하더라도 데이터의 결과는 순차 실행한 것처럼 올바르게 만들자

는 것이다.


9. MVCC는 왜 등장할까?

격리성을 높이기 위해 모든 읽기와 쓰기에 Lock을 사용하면 데이터의 안전성은 높아질 수 있지만 동시성이 떨어질 수 있다.

예를 들어

Writer가 데이터 수정 중

       ↓

Reader가 기다림

같은 상황이 계속 발생하면 많은 요청을 동시에 처리하기 어렵다.

이를 해결하기 위해 PostgreSQL과 MySQL InnoDB 같은 DBMS에서는 **MVCC(Multi-Version Concurrency Control)**를 사용한다.

MVCC의 핵심 아이디어는

하나의 데이터에 여러 Version을 유지하고 각 트랜잭션에게 자신이 볼 수 있는 Version을 보여준다.

예를 들어

기존 데이터

User
name = Kim

T2가 이름을 수정한다.

새 Version

User
name = Lee

이때 이전 Version을 바로 없애지 않고 유지한다.

Version 1
name = Kim

Version 2
name = Lee

그러면 먼저 시작한 트랜잭션은 상황에 따라:

Version 1 → Kim

을 계속 읽고,

나중에 시작한 트랜잭션은

Version 2 → Lee

를 읽을 수 있다.

따라서 일반적인 Snapshot Read에서는

Reader
→ 이전 Version 읽기

Writer
→ 새로운 Version 생성

방식으로 읽기와 쓰기 사이의 불필요한 Blocking을 줄일 수 있다.

즉 MVCC는

여러 Version 유지
        ↓
트랜잭션별 Snapshot 제공
        ↓
읽기와 쓰기의 충돌 감소
        ↓
높은 동시성

을 위한 동시성 제어 방식이다.

다만 MVCC가 있다고 해서 모든 동시성 문제가 자동으로 해결되는 것은 아니다.

같은 데이터를 동시에 수정하는 경우에는 여전히 Lock이나 충돌 검사 등이 필요할 수 있다.


10. 격리 수준이 높으면 무조건 좋은가?

그렇지는 않다.

격리 수준이 높으면 데이터의 안전성은 높아지지만 그만큼

Lock 대기

트랜잭션 충돌

Rollback

Retry

등이 증가해 처리량이 떨어질 수 있다.

따라서 실제 시스템에서는

얼마나 강한 데이터 일관성이 필요한가?

동시에 얼마나 많은 요청을 처리해야 하는가?

충돌이 얼마나 자주 발생하는가?

를 고려해 격리 수준을 선택해야 한다.

예를 들어 단순한 게시글 조회와 금융 거래가 요구하는 데이터 일관성 수준은 다를 수 있다.

따라서

가장 높은 격리 수준을 선택하는 것이 목적이 아니라 서비스에 필요한 수준의 격리성을 선택하는 것이 중요하다.


11. 핵심 정리

트랜잭션과 격리 수준을 정리하면 다음과 같다.

개념의미
Transaction여러 DB 연산을 하나의 논리적인 작업으로 처리
COMMIT트랜잭션의 변경사항 확정
ROLLBACK트랜잭션의 변경사항 취소
Atomicity모두 성공하거나 모두 실패
ConsistencyDB의 규칙과 일관성 유지
Isolation동시 트랜잭션 사이의 간섭 제어
DurabilityCOMMIT된 데이터 영구 보장
Dirty ReadCOMMIT되지 않은 데이터 읽음
Non-Repeatable Read같은 행을 다시 읽었는데 값이 변경됨
Phantom Read같은 범위 조회에서 행이 추가되거나 사라짐
Lost Update한 트랜잭션의 수정이 다른 수정에 의해 사라짐
MVCC여러 Version을 이용해 동시성을 높이는 방식

격리 수준은

READ UNCOMMITTED
        ↓
READ COMMITTED
        ↓
REPEATABLE READ
        ↓
SERIALIZABLE

순으로 격리성이 높아진다고 볼 수 있다.

가장 중요한 흐름은

여러 SQL을 하나의 작업으로 묶는다
            ↓
        Transaction
            ↓
신뢰할 수 있는 처리를 위해 ACID 보장
            ↓
하지만 여러 Transaction이 동시에 실행됨
            ↓
       이상 현상 발생 가능
            ↓
   Isolation Level로 제어
            ↓
MVCC / Lock 등의 방식으로 실제 구현

이다.

즉 트랜잭션의 핵심은 단순히 @Transactional을 붙이는 것이 아니라

여러 작업의 원자성을 보장하고, 동시에 실행되는 트랜잭션 사이에서 데이터의 일관성을 어느 수준까지 지킬 것인지 결정하는 것

이라고 이해할 수 있다.

[CS] CI/CD
[CS] Redis 자료구조
© 2026 Evan. All Rights Reserved. / RSS / Sitemap
Powered by Astro