백엔드 데이터 인프라 17편. 트랜잭션의 ACID 보장과 BEGIN·COMMIT·ROLLBACK·SAVEPOINT 표준 패턴을 풀어쓴 학습 노트.
이 글은 백엔드 데이터 인프라 시리즈 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 의 자가 호출 함정.
(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 = 시작 시점 스냅샷 일관
시리즈 다른 편
- Part 1 PostgreSQL 튜토리얼: 1편 · 11편 SELECT · 13편 UPDATE · 14편 DELETE · 15편 외래 키 · 16편 VIEW · 17편 (현재 글)
시리즈 다음 글
다음 글(18편)에서는 윈도우 함수와 고급 SQL — ROW_NUMBER·RANK·LAG·LEAD·PARTITION BY.
공식 문서: PostgreSQL 18 — Tutorial: Transactions에서 더 자세한 사양을 확인할 수 있어요.