트랜잭션이란?(Transaction)
- 트랜잭션의 정의
트랜잭션(Transaction)의 사전적 의미는 거래이고,
컴퓨터 과학 분야에서의 트랜잭션(Transaction)은 "더이상 분할이 불가능한 업무처리의 단위"를 의미한다.
이것은 하나의 작업을 위해 더이상 분할될 수 없는 명령들의 모음,
즉, 한꺼번에 수행되어야 할 일련의 연산모음을 의미한다.
현실 예시: 계좌이체
트랜잭션을 가장 쉽게 이해할 수 있는 예는 계좌이체이다.
1. 부모님의 계좌에서 돈이 인출되고
2. 자녀의 계좌에 입금이 완료됐다.
만약 한쪽만 성공하고 다른 한쪽이 실패한다면 누군가는 돈을 잃거나 받지 못하게 되며, 이는 매우 심각한 문제가 된다.
따라서 "모두 성공하거나, 모두 실패해야 한다"는 요구사항을 만족시키기 위해
트랜잭션이라는 개념이 반드시 필요하다.
- 트랜잭션의 필요성
여러 데이터 변경 작업을 하나의 트랜잭션으로 묶으면, 모든 작업이 성공할 때만 DB에 반영된다.
중간에 하나라도 실패한다면, 전체 작업이 취소(롤백)된다.
이처럼 트랜잭션은 데이터의 신뢰성과 안전성을 보장하는 핵심 장치이다.
트랜잭션의 SQL 예시
sql
START TRANSACTION;
-- 아래 두 명령이 하나의 단위로 처리됨
A 계좌에서 인출;
B 계좌로 입금;
COMMIT; -- 두 작업이 모두 성공하면 확정
만약 중간에 오류가 발생한다면 ROLLBACK;을 실행하여 ( * 원자성을 다룰 떄 다시 다루겠음!) 모든 변경 사항을 되돌릴 수 있다.
핵심 요약
- 트랜잭션은 더 이상 나눌 수 없는 작업의 묶음이다.
- 트랜잭션에 포함된 작업은 모두 성공해야 커밋된다.
- 하나라도 실패하면 전체가 롤백된다.
- 데이터의 정합성과 신뢰성을 보장하기 위해 필수적인 개념이다.
- 트랜잭션이 없으면 어떤 문제가 발생할까?
예를 들어 회원 가입 중 닉네임은 저장됐지만 이메일이나 비밀번호는 저장되지 못했다면,
불완전한 회원 데이터가 DB에 남게 되는 문제가 발생할 수 있다.
이러한 상황에서는 “데이터 정합성”이 깨지게 되며,이후의 인증 로직, 조회, 업데이트 등 여러 기능이 제대로 동작하지 않게 된다.
*데이터 정합성이란?
데이터 정합성(Integrity)이란 데이터가 항상 논리적이고 신뢰할 수 있는 상태를 유지하는 것을 의미한다.
쉽게 말해 “이 데이터는 믿을 수 있는 상태인가?”라는 질문에"그렇다"고 말할 수 있어야 한다.
정합성 예시
- 회원 가입 시: 닉네임, 이메일, 비밀번호 등 모든 정보가 함께 저장되어야 한다.
- 계좌 이체 시: 송금인과 수신인의 잔액 변화가 서로 일관되어야 한다.
트랜잭션의 4가지 원칙: ACID
- 우선 간단한 표로 정리하자면 다음과 같다.

- How to Ensuarnce ACID? - ACID 원칙을 보장하려면?
— 트랜잭션은 어떻게 ACID 원칙을 보장하는가
ACID는 트랜잭션이 지켜야 할 4가지 핵심 원칙이다.
그렇다면 데이터베이스는 이 원칙들을 어떻게 실제로 보장하고 있을까?
이 섹션에서는 각각의 원칙을 구현 수준에서 살펴보고, 트랜잭션이 신뢰성을 유지하는 메커니즘을 설명한다.
1️⃣ 원자성 (Atomicity)
트랜잭션은 모두 수행되거나, 전혀 수행되지 않아야 한다.
DBMS는 변경 내역을 바로 실제 테이블에 반영하지 않고
일시적으로 별도의 임시 공간에 저장해둔다.
- 이 임시 저장소를 Rollback Segment(롤백 세그먼트)라고 한다.
- 트랜잭션 수행 중 문제가 발생하면, 이 롤백 세그먼트를 이용해 모든 변경 사항을 되돌린다.
- 반대로 트랜잭션이 정상적으로 끝나면, 해당 변경 내역을 실제 테이블에 커밋한다.

🧱 Rollback Segment 구성 요소와 역할
- Old image : 변경 전 데이터 저장(UNDO용)
- Table : 어떤 테이블 / 레코드가 변경 됐는지 메타데이터 저장
- New image : (일반적으로 저장하지 않음) 변경 후 데이터는 실제 테이블에 반영됨
DML 작동시 Segment 동작 흐름
- 1. 사용자가 DML 작업 실행
(예: UPDATE, INSERT, DELETE 등)
- 2. 변경 전 데이터(Old Image)→ Rollback Segment에 저장됨→ 나중에 Rollback 시 복구 용도로 사용
- 3. 변경 후 데이터(New Image)→ 실제 Table에 반영됨
📌 Save Point란?
트랜잭션이 길어지면, 확실히 성공한 부분까지 다시 수행해야 하는 비효율이 발생할 수 있다.
이를 방지하기 위해 Save Point(세이브 포인트)라는 중간 저장 지점을 설정할 수 있다.
- 오류 발생 시 전체를 되돌리는 것이 아니라,
- 특정 세이브 포인트까지만 롤백하여 작업을 부분 복구할 수 있다.
BEGIN;
-- 1단계: 아주 중요함 (예: 돈 출금)
UPDATE account SET balance = balance - 500 WHERE id = 1;
-- SAVEPOINT: 여기까진 무조건 살리고 싶음
SAVEPOINT after_main_task;
-- 2단계: 덜 중요 (예: 보너스 로그 기록)
INSERT INTO bonus_log ...;
-- 3단계: 덜 중요 (예: 알림 전송 기록)
INSERT INTO notification_log ...;
-- 2단계 또는 3단계에서 오류 발생 시
ROLLBACK TO after_main_task; -- 1단계는 그대로 유지, 2~3단계만 무효화
-- 트랜잭션 확정 (1단계 결과만 반영)
COMMIT;
SAVEPOINT는 트랜잭션 내에서핵심 작업은 살리고, 부가 작업은 조건부로 롤백 할 수 있게 해준다.
이걸 통해 트랜잭션의 원자성을 유지하면서도, 유연한 오류 대응이 가능하다
2️⃣ 일관성 (Consistency)
트랜잭션이 수행되기 전과 후에 데이터는 항상 정해진 규칙을 만족해야 한다.
DB는 스키마, 제약조건, 트리거 등을 통해 데이터의 무결성 규칙을 정의하고,
트랜잭션이 이를 깨뜨리지 않도록 강제한다.
- ex) 외래 키 제약(FK), NOT NULL, UNIQUE, CHECK 등
💡 예시 상황:
- 계좌 잔액이 마이너스가 되어서는 안 되는 시스템이라면
트랜잭션 중간에 그런 상태가 발생하면 즉시 실패한다.
3️⃣ 격리성 (Isolation)
동시에 실행되는 트랜잭션들이 서로 영향을 주지 않도록 보장해야 한다.
DB는 여러 트랜잭션이 동시에 접근하더라도 논리적으로는 순차적으로 실행된 것처럼 보이도록 처리한다.
이를 위해 Isolation Level(격리 수준)을 조절한다.
격리 수준(Isolation Level)이란?
트랜잭션 간 서로 얼마나 간섭 없이 고립되어야 하는지를 결정하는 설정이다.
레벨이 높을수록 트랜잭션간 고립정도가 높아지지만, 성능 저하도 야기된다.
일반적인 온라인 서비스에서는 READ COMMITTED나 REPEATABLE READ 중 하나를 사용한다.

- READ COMMITTED, REPEATABLE READ, SERIALIZABLE 등의 수준에 따라
- 락(Lock), MVCC(Multi-Version Concurrency Control), 스냅샷 등 다양한 기법이 활용된다.
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void checkInventory() {
// 조회 시점의 재고 스냅샷을 유지
}
4️⃣ 지속성 (Durability)
트랜잭션이 커밋된 이후에는, 설령 시스템에 장애가 발생하더라도 결과는 반드시 보존되어야 한다.
DB는 커밋이 발생하면 해당 변경 내용을 디스크에 안전하게 기록한다.
이때 사용되는 주요 메커니즘이 바로 로그 파일(Write-Ahead Logging, WAL)이다.
- 로그에 먼저 쓰고, 실제 데이터 파일은 나중에 반영
- 장애 발생 시 로그를 이용해 트랜잭션 복구 가능
* 아래 그림은 지속성을 보장하기 위한 WAL(Write-Ahead Logging) 구조를 개념적으로 표현한 것이다.
트랜잭션이 먼저 로그 파일에 기록되고, 이후 DB의 실제 데이터 블록에 반영된다.
장애가 발생하더라도 로그를 이용해 트랜잭션을 복구할 수 있다.

트랜잭션 상태

💡트랜잭션 상태(state) 정리:
트랜잭션은 수행 도중 여러 상태를 거치며, 성공 또는 실패 여부에 따라 아래와 같이 5가지 상태로 전이된다.
🔹 1. 활성(Active) : 트랜잭션이 시작되고, SQL 연산이 수행 중인 상태 데이터 변경(insert, update, delete 등)이 메모리(DBMS 내부 버퍼)에 반영되지만 아직 DB에 확정되지 않음
🔹 2. 부분 완료(Partially Committed) 마지막 명령까지 정상적으로 실행된 상태 이제 DB에 commit만 남은 시점 이때 시스템 장애가 발생하면 rollback이 가능함
🔹 3. 완료(Committed) " 트랜잭션이 완전히 성공적으로 종료되고, 모든 변경 사항이 DB에 반영(Commit)된 상태 장애가 나더라도 이 변경 내용은 지속성(Durability) 원칙에 따라 보장됨
🔹 4. 실패(Failed) : 트랜잭션 실행 중 오류가 발생한 상태 예: SQL 예외, 무결성 위반, 장비 오류 등 이 상태가 되면 트랜잭션은 Rollback 절차를 통해 이전 상태로 복구 시도
🔹 5. 철회(Aborted) " 실패 상태에서 롤백이 완료된 상태 트랜잭션 실행 전 상태로 복구됨 필요에 따라 사용자가 다시 트랜잭션을 시도할 수 있음
내가 생각하는 트랜잭션이 필요한 실무 상황
다음과 같은 경우에는 반드시 트랜잭션이 사용되어야 한다.
- 여러 데이터(table, row 등)를 동시에 변경하는 작업
- 주문 시 주문 테이블과 결제 테이블을 함께 저장해야 할 경우
- 게시글 작성 시 본문과 첨부파일을 함께 저장해야 할 경우
- 중간 실패가 절대 허용되지 않는 비즈니스
- 금융, 결제, 포인트 차감 등
- 일부만 처리되면 사용자 신뢰에 치명적 영향
- 동시성 이슈(여러 사용자의 동시 접근)가 있는 경우
- 좌석 예매, 한정 수량 이벤트 등
- 경쟁 상황에서 데이터 꼬임 방지 필요
- 트랜잭션이 필요하지 않은 경우
반대로 다음과 같은 경우에는 굳이 트랜잭션을 걸지 않아도 된다.
- 단순 조회(SELECT)
- 단, 정합성을 보장해야 하는 복잡한 통계, 조회는 예외
⚠️ ex) 재고 현황, 잔액 등 정확히 같은 시점의 데이터를 읽어야 하는 경우, 다른 트랜잭션이 중간에 데이터를 변경하지 못하도록 하기 위해 트랜잭션 + 격리 레벨 설정이 필요할 수 있다.
서버(Spring) 계층에서 트랜잭션은 어떻게 동작할까?
Spring에서는 @Transactional 애노테이션을 통해 트랜잭션을 선언적으로 처리할 수 있다. 하지만 Spring 자체가 트랜잭션을 실행하는 주체는 아니다.
트랜잭션은 실제로는 DB(DataSource + JDBC + ORM) 계층에서 동작하며, Spring은 이를 제어(관리) 하는 역할을 한다.
Spring 트랜잭션의 실행 구조
1. Spring은 AOP(Aspect-Oriented Programming) 기반으로 @Transactional이 붙은 메서드를 감싼다.
2. 해당 메서드가 호출되면, 프록시(proxy) 객체가 트랜잭션을 시작한다.
3. 내부에서 JDBC/Hibernate/JPA 등을 통해 실제 DB 작업이 수행된다.
4. 메서드가 정상 종료되면 트랜잭션을 commit하고,예외(RuntimeException 또는 Error) 발생 시 자동으로 rollback 한다.
Spring 트랜잭션 흐름 요약 (JPA 기준)
Client → Service( @Transactional ) → Repository(JPA) → EntityManager → DB
- 트랜잭션은 Service 계층에 걸어주는 것이 일반적이다.
- Repository는 실제 DB와의 상호작용만 담당한다.
- EntityManager는 JPA의 트랜잭션 처리 매니저이며, Spring이 이를 감싼다.
실제 코드에서의 트랜잭션 사용
Spring에서는 @Transactional 애노테이션을 통해 트랜잭션을 쉽게 적용할 수 있다.
다음은 실제 내 프로젝트에서 주문 생성에 트랜잭션이 적용된 예시이다.

// 주문 생성 로직 (트랜잭션 단위로 처리)
@Transactional // 이 메서드 전체를 하나의 트랜잭션으로 처리한다.
public OrderResponse creatOrder(InstantOrderRequestDto request) {
// [비DB 작업] 현재 로그인된 사용자 ID 조회
// Spring Security 등 서버 메모리 내에서 처리된다
//이 시점에는 DB에 접근하지 않았기 때문에 실제 트랜잭션(DB 커넥션)은 아직 시작되지 않은 상태이다.
Long userId = SecurityUtil.getCurrentUserId();
// [DB 접근 시작] 와인 정보 조회 (SELECT)
// 이 시점에 JPA는 실제로 DB 커넥션을 얻고 트랜잭션을 시작함 (Lazy transaction begin)
Wine wine = wineRepo.findById(request.getWineId())
.orElseThrow(() -> new BadRequestException("와인 정보가 없습니다."));
// [비DB 작업] 주문 항목 생성 (메모리상 VO/엔티티 구성)
OrderItem orderItem = OrderItem.of(wine, request.getQuantity());
// [DB 작업] 주문 엔티티 생성 및 저장 (INSERT)
// JPA는 내부적으로 트랜잭션 컨텍스트에 변경 내용을 반영하고, flush 시 DB에 적용
Order order = Order.create(userId, List.of(orderItem));
Order savedOrder = orderRepository.save(order);
// [비DB 작업] 응답 DTO로 변환
return OrderResponse.from(savedOrder);
/*
* ⚙️ 트랜잭션 처리 흐름
* - 위 메서드 실행 시 Spring AOP 프록시가 트랜잭션을 시작함
* - DB 작업들이 하나의 트랜잭션 내에서 처리됨
* - 예외 없이 정상 종료되면 자동으로 commit 수행됨
* - 중간에 RuntimeException 발생 시 rollback 처리됨
*/
}
실제 동작 흐름
- 메서드 진입 전, Spring의 트랜잭션 프록시가 DB 트랜잭션 시작을 요청함.
- DB 커넥션이 열리고 setAutoCommit(false) 상태로 변경됨.
- 아직 DB에는 반영되지 않고, 변경은 메모리(버퍼)에만 있음.
- 메서드 내부의 로직이 실행됨.
- DB 조회, 엔티티 생성, save() 같은 작업이 수행됨.
- 이 때 JPA는 내부적으로 EntityManager를 통해 변경사항을 추적함.
- 메서드가 예외 없이 정상 종료되면
- Spring이 자동으로 commit 요청을 보냄 → DB에 실제 반영
- 메서드 중간에 RuntimeException이 발생하면
- Spring은 rollback 요청 → 트랜잭션 시작 시점으로 되돌림
- DB에는 아무런 변경도 남지 않음
- 실제 트랜잭션을 시작하고 커밋/롤백하는 주체는 DB다.
- Spring은 그걸 언제 시작하고, 언제 끝낼지를 결정해주는 제어자(controller) 역할을 한다.
✔️ 주의 할점 :
CheckedException은 rollback 안 됨 (Spring 기본 설정 기준)
Spring의 @Transactional은 RuntimeException 또는 Error만 자동으로 rollback한다.
CheckedException은 commit됨
* 타 블로그 참조
CheckedException Vs UnCheckedException
Checked Exception, Unchecked Exception 그리고 예외 처리
Java개발자라면 꼭 알아야할 Exception과 Exception 처리에 대해 알아보자
velog.io
@Transactional public void doSomething() throws IOException {
// 이 경우 IOException 발생해도 rollback 안 됨!
}
⚠️ Spring에서는 기본적으로 RuntimeException이나 Error가 발생해야 rollback이 되고,
CheckedException은 rollback 대상이 아니다. 이를 변경하려면 @Transactional(rollbackFor = ...)을 명시해야 한다.
트랜잭션 체크리스트
✅ 아래 중 하나라도 해당되면 트랜잭션이 필요한 사항이라 생각하고 개발에서 쓰자.
- 여러 데이터가 동시에 바뀌는가?
- 중간 실패가 허용되지 않는가?
- 동시성 문제가 발생할 수 있는가?
단 ,이러한 조건이 아니면 트랜잭션은 오히려 불필요한 오버헤드가 될 수 있다!
마무리
트랜잭션은 단순한 기술 옵션이 아니라, 데이터의 정합성과 사용자 신뢰를 지키기 위한 핵심 장치다.
특히 주문, 결제, 포인트, 재고 등 꼭 한 덩어리로 처리돼야 하는 작업이 있는 도메인에서는 트랜잭션이 필수다.
하지만 반대로, 단순 조회나 변경이 없는 작업까지 무조건 트랜잭션을 거는 건 오히려 불필요한 오버헤드가 될 수 있다.
상황에 맞게 트랜잭션을 적용하고, 그 내부 동작을 이해하며 쓰도록 노력해야겠다.
