백엔드 데이터 인프라 17편 — 트랜잭션 BEGIN COMMIT ROLLBACK

2026-05-17백엔드 데이터 인프라

백엔드 데이터 인프라 17편. 트랜잭션의 ACID 보장과 BEGIN·COMMIT·ROLLBACK·SAVEPOINT 표준 패턴을 풀어쓴 학습 노트.

📚 백엔드 데이터 인프라 · 17편 — 트랜잭션 BEGIN COMMIT ROLLBACK

이 글은 백엔드 데이터 인프라 시리즈 70편 중 17편이에요. 13~14편 UPDATE·DELETE 에서 "운영 5단계 (BEGIN-SELECT-UPDATE-검증-COMMIT)" 안 본 BEGIN·COMMIT — 그게 바로 트랜잭션.

트랜잭션 한 줄 설명

여러 SQL을 한 묶음으로 처리. "모두 성공 또는 모두 실패". 한 묶음 안 어떤 SQL이 실패하면 — 그 묶음의 모든 변경이 취소.

전형 예 — 계좌 이체:

BEGIN;
    UPDATE accounts SET balance = balance - 10000 WHERE id = 1;   -- A 출금
    UPDATE accounts SET balance = balance + 10000 WHERE id = 2;   -- B 입금
COMMIT;

A에서 빼고 B에 더하는 — 두 SQL이 둘 다 성공해야 이체 완료. 첫 번째만 성공하고 두 번째 실패 = A 돈 사라짐. 트랜잭션 = 이 위험 차단.

ACID — 트랜잭션의 4가지 약속

7편 관계형 모델 에서 짧게 소개한 ACID 깊이.

이름 의미
A Atomicity (원자성) 모두 성공 또는 모두 실패
C Consistency (일관성) 제약 조건 항상 만족
I Isolation (격리성) 동시 트랜잭션 서로 안 보임
D Durability (영속성) 커밋 후엔 사라지지 않음

PG는 ACID 100% 보장. "DB가 약속해주는 안전망".

기본 3가지 명령

BEGIN — 트랜잭션 시작

BEGIN;
-- 또는 START TRANSACTION;

이 시점부터 다음 COMMIT 또는 ROLLBACK 까지가 한 트랜잭션.

COMMIT — 확정

COMMIT;

지금까지의 변경을 영구 적용. WAL에 기록 → 디스크 → 다른 트랜잭션에 보임.

ROLLBACK — 취소

ROLLBACK;

지금까지의 변경을 모두 취소. 트랜잭션 시작 전 상태로.

자동 커밋 vs 명시 트랜잭션

PG 기본 = 자동 커밋. 즉 각 SQL이 "개별 트랜잭션".

UPDATE users SET name = 'Alice' WHERE id = 1;   -- 자동으로 BEGIN·COMMIT

명시적으로 묶고 싶으면 BEGIN.

BEGIN;
UPDATE users SET name = 'Alice' WHERE id = 1;
UPDATE users SET email = 'new@example.com' WHERE id = 1;
COMMIT;

JPA @Transactional (자바 백엔드 입문 43편) = 자동으로 BEGIN·COMMIT 박는 어노테이션.

SAVEPOINT — 부분 ROLLBACK

BEGIN;
    INSERT INTO orders (user_id, amount) VALUES (1, 10000);
    SAVEPOINT after_order;

    UPDATE inventory SET stock = stock - 1 WHERE product_id = 100;
    -- 재고 부족 → 롤백
    ROLLBACK TO SAVEPOINT after_order;

    UPDATE orders SET status = 'PENDING_STOCK' WHERE id = <new_order_id>;
COMMIT;

큰 트랜잭션 안에 "부분 취소점" 박기. 복잡한 비즈니스 로직에서 가끔.

에러 후 트랜잭션

PG 트랜잭션 안에서 SQL 에러 발생 시 — 이후 모든 SQL 거부.

BEGIN;
SELECT * FROM nonexistent_table;    -- 에러
SELECT 1;                            -- ERROR: current transaction is aborted
COMMIT;                              -- ROLLBACK 동작

해결 — 에러 후 ROLLBACK 또는 SAVEPOINT 활용.

격리 수준 — Isolation Levels

여러 트랜잭션이 동시에 실행될 때 "서로 어떻게 보이나". 4가지 수준 — 38편 MVCC 에서 깊이.

수준 의미
READ UNCOMMITTED 다른 트랜잭션 미커밋 변경 보임 (PG는 지원 X — READ COMMITTED 처리)
READ COMMITTED (PG 기본) 커밋된 변경만 보임
REPEATABLE READ 트랜잭션 시작 시점 스냅샷 일관
SERIALIZABLE 완벽한 직렬화 (성능 비용)

PG 기본 = READ COMMITTED. 거의 모든 한국 회사 적정.

BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 또는 BEGIN ISOLATION LEVEL REPEATABLE READ;
...
COMMIT;

락 — FOR UPDATE·FOR SHARE

행 단위 락으로 "동시 변경 차단".

BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;   -- 이 행 락 획득
UPDATE accounts SET balance = balance - 10000 WHERE id = 1;
COMMIT;

FOR UPDATE = 다른 트랜잭션은 이 행 UPDATE·DELETE 못 함 (커밋·롤백까지 대기).

SELECT * FROM products WHERE id = 1 FOR SHARE;   -- 공유 락 (UPDATE만 차단)
SELECT * FROM products WHERE id = 1 FOR UPDATE SKIP LOCKED;   -- 락된 행은 건너뜀
SELECT * FROM products WHERE id = 1 FOR UPDATE NOWAIT;   -- 락 대기 X (즉시 에러)

SKIP LOCKED = 큐 처리에 매우 유용 (Kafka 대안). NOWAIT = 락 충돌 즉시 감지.

운영 시나리오 — 5가지

(1) 계좌 이체

BEGIN;
UPDATE accounts SET balance = balance - 10000 WHERE id = 1;
UPDATE accounts SET balance = balance + 10000 WHERE id = 2;
INSERT INTO transfers (from_id, to_id, amount) VALUES (1, 2, 10000);
COMMIT;

(2) 주문 + 재고 + 결제

BEGIN;
INSERT INTO orders (...) RETURNING id;
UPDATE inventory SET stock = stock - 1 WHERE product_id = 100;
INSERT INTO payments (order_id, amount, status) VALUES (...);
COMMIT;

(3) 데이터 이관 (멱등)

BEGIN;
INSERT INTO archive SELECT * FROM logs WHERE created_at < '2025-01-01';
DELETE FROM logs WHERE created_at < '2025-01-01';
COMMIT;

(4) 통계 갱신 (재시도 가능)

BEGIN;
TRUNCATE daily_stats;
INSERT INTO daily_stats SELECT ... FROM orders GROUP BY ...;
COMMIT;

(5) 큐 처리 (FOR UPDATE SKIP LOCKED)

BEGIN;
SELECT * FROM job_queue
WHERE status = 'PENDING'
ORDER BY created_at
LIMIT 10
FOR UPDATE SKIP LOCKED;
-- 잡은 작업들 처리...
UPDATE job_queue SET status = 'PROCESSED' WHERE id IN (...);
COMMIT;

여러 워커가 동시에 큐 처리 가능 — 중복 처리 0.

Spring JPA 의 트랜잭션

자바 백엔드 입문 43편 @Transactional 핵심.

@Service
public class TransferService {

    @Transactional
    public void transfer(Long fromId, Long toId, int amount) {
        accountRepository.decrementBalance(fromId, amount);
        accountRepository.incrementBalance(toId, amount);
        transferRepository.save(new Transfer(fromId, toId, amount));
    }
}

@Transactional = 메서드 시작 시 BEGIN, 정상 종료 시 COMMIT, 예외 시 ROLLBACK 자동. 자바 백엔드의 표준 패턴.

격리 수준 명시

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void process() { ... }

트랜잭션 + 외부 API 함정

@Transactional
public void process() {
    repository.save(...);
    externalApi.send(...);    // ❌ 외부 호출 안 트랜잭션
    repository.update(...);
}

외부 API 호출이 "트랜잭션 안에" 들어가면 — DB 락이 외부 API 응답 시간 동안 잡혀 다른 트랜잭션 대기. OutBox 패턴·ApplicationEvent (자바 백엔드 입문 38편) 로 분리.

함정 5가지

(1) 트랜잭션 안 외부 호출

위 함정. 외부 호출은 트랜잭션 밖 (이벤트·Outbox).

(2) 너무 큰 트랜잭션

100만 행 UPDATE 한 트랜잭션 = WAL 폭발·락 폭주. chunk 분할.

(3) 자동 커밋 모드에서 BEGIN 잊음

UPDATE users SET ...;   -- 자동 커밋, ROLLBACK 불가

운영 = 명시적 BEGIN.

(4) 에러 후 다음 SQL 실행 시도

에러 후 — 모든 SQL 거부. ROLLBACK 또는 SAVEPOINT.

(5) @Transactional 자가 호출

public void caller() {
    save(...);   // ❌ @Transactional 안 먹힘 (프록시 회피)
}

@Transactional
public void save(...) { ... }

자바 백엔드 입문 25편 AOP 의 자가 호출 함정.

🎯 트랜잭션 5룰

(1) 명시적 BEGIN·COMMIT. (2) 외부 API는 트랜잭션 밖. (3) 큰 작업은 chunk. (4) 에러 후 ROLLBACK. (5) JPA는 @Transactional 자동. FOR UPDATE SKIP LOCKED는 큐 처리 핵심.

한 줄 정리 — 트랜잭션 = ACID 묶음. BEGIN·COMMIT·ROLLBACK + SAVEPOINT. 격리 수준 4단계 (PG 기본 = READ COMMITTED). FOR UPDATE 행 락, SKIP LOCKED는 큐. JPA는 @Transactional 자동. 외부 API는 트랜잭션 밖.

시험 직전 한 번 더 — 트랜잭션 입문자가 매번 헷갈리는 것

  • 트랜잭션 = 여러 SQL 한 묶음 (모두 성공 또는 모두 실패)
  • BEGIN = 시작 (또는 START TRANSACTION)
  • COMMIT = 확정
  • ROLLBACK = 취소
  • ACID = Atomicity·Consistency·Isolation·Durability
  • 자동 커밋 = PG 기본 (각 SQL이 개별 트랜잭션)
  • 명시 트랜잭션 = BEGIN ... COMMIT
  • SAVEPOINT = 부분 취소점
  • ROLLBACK TO SAVEPOINT name
  • 에러 후 = 이후 SQL 거부 (ROLLBACK 또는 SAVEPOINT)
  • 격리 수준 4 = READ UNCOMMITTED·READ COMMITTED·REPEATABLE READ·SERIALIZABLE
  • PG 기본 = READ COMMITTED
  • FOR UPDATE = 행 락 (UPDATE·DELETE 차단)
  • FOR SHARE = 공유 락
  • SKIP LOCKED = 락된 행 건너뜀 (큐 처리 표준)
  • NOWAIT = 락 대기 X
  • @Transactional = JPA 자동 트랜잭션
  • @Transactional 자가 호출 = 안 먹힘 (프록시)
  • 외부 API는 트랜잭션 밖 = Outbox·이벤트 패턴
  • 큰 작업 = chunk 분할
  • WAL = 트랜잭션 영속성 보장 메커니즘
  • READ COMMITTED = 커밋된 변경만 보임
  • REPEATABLE READ = 시작 시점 스냅샷 일관

시리즈 다른 편

시리즈 다음 글

다음 글(18편)에서는 윈도우 함수와 고급 SQL — ROW_NUMBER·RANK·LAG·LEAD·PARTITION BY.

공식 문서: PostgreSQL 18 — Tutorial: Transactions에서 더 자세한 사양을 확인할 수 있어요.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!