트랜잭션이란?
트랜잭션(Transaction)은 데이터베이스에서 하나의 논리적인 작업 단위를 의미한다. 여러 개의 데이터 조작 연산(삽입, 삭제, 갱신 등)이 모여 하나의 트랜잭션을 구성하며, 트랜잭션이 완료되지 않으면 데이터베이스 상태가 변경되지 않는다.
트랜잭션의 필요성
- 데이터 정합성 보장: 연산 도중 장애가 발생하면 데이터 불일치가 발생할 수 있다.
- 동시성 제어: 다수의 사용자가 동시에 데이터를 조작할 경우, 일관성을 유지해야 한다.
- 데이터 복구 가능: 장애 발생 시, 이전 상태로 복구할 수 있다.
트랜잭션 활용 사례
- 온라인 쇼핑몰 결제 시스템: 결제 과정 중 장애가 발생하면 모든 작업이 롤백됨
- 은행 계좌 이체: 한 계좌에서 출금하고 다른 계좌에 입금하는 과정이 하나의 트랜잭션으로 묶여야 함
- 항공권 예약 시스템: 동일한 좌석을 여러 사용자가 예약하는 문제를 방지하기 위해 트랜잭션을 활용함
트랜잭션의 주요 연산
| 연산 | 설명 | 사용 예시 |
| BEGIN (START TRANSACTION) | 트랜잭션 시작 | BEGIN; 또는 START TRANSACTION; |
| COMMIT | 트랜잭션을 정상적으로 완료하고 변경 사항을 반영 | COMMIT; |
| ROLLBACK | 트랜잭션이 실패 시 변경 사항을 취소하고 이전 상태로 되돌림 | ROLLBACK; |
| SAVEPOINT | 트랜잭션 내 특정 지점까지 롤백할 수 있도록 설정 | SAVEPOINT save1; |
| ROLLBACK TO SAVEPOINT | 특정 세이브포인트(Savepoint) 까지만 롤백 | ROLLBACK TO SAVEPOINT save1; |
| RELEASE SAVEPOINT | 특정 세이브포인트를 삭제하여 더 이상 롤백할 수 없게 함 | RELEASE SAVEPOINT save1; |
| SET TRANSACTION | 트랜잭션의 격리 수준을 설정 | SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; |
* [참고] 데이터의 무결성과 정합성
데이터의 무결성과 정합성
무결성은 데이터의 정확성을 보장하는 기술적 개념이라면, 정합성은 데이터가 의미적으로 올바르게 유지됨을 보장하는 개념이라고 볼 수 있다.
데이터 무결성(Integrity)
데이터베이스 내의 데이터가 정확하고 일관된 상태를 유지하는 것이다. 무결성은 데이터의 신뢰성과 정확성을 보장하는 중요한 요소이며, 이를 위반하면 데이터베이스가 잘못된 정보를 포함하게 될 수 있다. 무결성을 보장하기 위해 데이터베이스에서는 아래와 같이 다양한 무결성 제약 조건을 적용한다.
- 개체 무결성(Entity Integrity): 기본 키(Primary Key)는 유일해야 하며, NULL 값을 가질 수 없다.
- 참조 무결성(Referential Integrity): 외래 키(Foreign Key)는 참조하는 테이블의 기본 키와 일치해야 한다.
- 도메인 무결성(Domain Integrity): 특정 컬럼이 허용된 데이터 타입과 범위를 벗어나지 않아야 한다.
데이터 정합성(Consistency)
정합성은 데이터가 의미적으로 일관되며 논리적으로 맞아야 함을 의미한다. 즉, 데이터베이스 내의 모든 데이터가 서로 모순되지 않고 올바르게 연결되어 있어야 한다.
- 정합성은 트랜잭션이 시작되기 전과 후에도 유지되어야 한다.
- 트랜잭션 수행 도중 정합성이 깨지는 경우, 트랜잭션이 롤백(ROLLBACK)되어야 한다.
- 예시: 은행 계좌 이체에서 한 계좌에서 출금된 금액이 다른 계좌에 정상적으로 입금되지 않으면 정합성이 깨지게 된다.
트랜잭션의 ACID 특성
트랜잭션이 안전하게 수행되기 위해 따라야 하는 4가지 기본 원칙을 ACID 특성이라 한다.
Atomicity(원자성)
- 트랜잭션은 모두 실행되거나, 전혀 실행되지 않거나 해야 한다.
- 부분 실행이 허용되지 않으며, 오류 발생 시 모든 작업이 취소(Rollback)된다.
- 예시: 은행 계좌 이체
- 고객 A가 고객 B에게 100,000원을 이체한다고 가정
- 이체 트랜잭션은 1) A의 계좌에서 100,000원 출금 → 2) B의 계좌에 100,000원 입금 두 개의 작업으로 이루어짐
- 만약 출금이 완료되었지만 입금 과정에서 오류가 발생하면 전체 트랜잭션이 롤백(Rollback)되어 출금도 취소됨
- 즉, 모든 작업이 실행되거나 아무것도 실행되지 않음
* [참고] Atomicity(원자성)를 보장하는 기술
| 기술 | 설명 | Atomicity(원자성) 보장 방식 |
| Undo Log (롤백 로그) | 트랜잭션이 실패하면 변경 사항을 이전 상태로 되돌림 | 실패한 트랜잭션을 원래 상태로 복구 |
| Two-Phase Commit (2PC, 이단계 커밋) | 분산 트랜잭션에서 모든 노드가 커밋을 동의해야 적용 | 한 노드라도 실패하면 전체 트랜잭션을 롤백 |
| Savepoint (세이브포인트) | 트랜잭션 중 특정 지점까지 롤백 가능 | 부분 롤백이 가능하여 원자성 강화 |
| Crash Recovery (충돌 복구 시스템) | 시스템 장애 발생 시 트랜잭션의 완료 여부를 검사 후 롤백/재적용 | 충돌 발생 시 손상된 트랜잭션을 복구 |
| Atomic File System Operations (원자적 파일 시스템 연산) | 파일 변경 작업이 중간 상태 없이 완전히 적용되거나 취소됨 | 데이터베이스 외부에서도 원자성을 보장 |
Consistency(일관성)
- 트랜잭션 수행 전후에 데이터베이스는 일관된 상태를 유지해야 한다.
- 무결성 제약 조건이 보장되어야 한다.
- 예시: 재고 관리 시스템
- 상품 재고가 0인 상태에서 고객이 주문한다고 가정
- 트랜잭션이 정상적으로 동작하면, 주문이 접수되기 전에 시스템이 재고를 확인하여 주문을 차단해야 함
- 만약 일관성이 깨진다면 고객이 주문을 했는데도 실제 창고에는 물건이 없는 오류가 발생할 수 있음
- 무결성 제약 조건 (Primary Key, Foreign Key 등)을 통해 데이터 무결성을 유지해야 함
* [참고] 무결성 제약 조건 종류
| 무결성 제약 조건 | 설명 | 사용 예시 |
| Primary Key (기본 키) | 테이블에서 각 행을 고유하게 식별하는 키, NULL 불가 & 중복 불가 | user_id INT PRIMARY KEY |
| Foreign Key (외래 키) | 다른 테이블의 기본 키를 참조하여 데이터 정합성을 유지 | FOREIGN KEY (user_id) REFERENCES Users(user_id) |
| Unique (유니크 제약 조건) | 특정 컬럼의 값이 중복되지 않도록 제한 | email VARCHAR(100) UNIQUE |
| Check (체크 제약 조건) | 특정 조건을 만족하는 값만 허용 | price DECIMAL(10,2) CHECK (price > 0) |
| Not Null (널 제한) | NULL 값을 허용하지 않음 | `name VARCHAR(50) NOT NULL` |
| Default (기본값 설정) | 컬럼에 값이 입력되지 않았을 때 기본값을 제공 | status VARCHAR(20) DEFAULT 'active' |
| Cascade (CASCADE 옵션) | 부모 테이블 변경 시 자식 테이블에도 반영됨 | ON DELETE CASCADE ON UPDATE CASCADE |
| Trigger (트리거) | 특정 이벤트 발생 시 자동 실행되는 SQL 코드 | BEFORE UPDATE ON Accounts |
Isolation(격리성)
- 여러 트랜잭션이 동시에 실행될 때, 서로의 작업이 영향을 미치지 않도록 격리해야 한다. (서로 독립적)
- 가장 엄격할 경우 순차적으로 트랜잭션을 실행한다.
- 이를 구현하기 위한 방법으로는 트랜잭션 격리 수준을 조정하거나, 데이터베이스 락(Lock)을 사용한다.
- 예시: 항공권 예약 시스템
- 두 명의 사용자가 동시에 같은 항공편의 마지막 좌석을 예약하려고 시도함
- 트랜잭션 격리 수준을 설정하지 않으면, 두 트랜잭션이 동시에 같은 좌석을 예약해버리는 문제가 발생할 수 있음
- REPEATABLE READ 또는 SERIALIZABLE 격리 수준으로 설정하면 한 트랜잭션이 완료될 때까지 다른 트랜잭션이 좌석을 예약하지 못하도록 차단됨
Durability(지속성)
- 트랜잭션이 성공적으로 수행되면, 그 변경 사항은 영구적으로 데이터베이스에 저장된다.
- 시스템 장애가 발생해도 데이터(트랜잭션의 결과)가 손실되지 않는다. -> 주로 로그로 남고 성공하면 로그에 저장(로그를 활용하여 장애 해결)
- 트랜잭션 관리 기법(Log, Checkpoint, Undo/Redo)은 지속성을 보장하기 위한 핵심 기술
- 로그(Log) 기반 복구 (WAL, Redo Log) : 장애 발생 시 변경 사항을 재적용할 수 있도록 기록, 변경 사항을 먼저 로그에 기록하여 장애 발생 시 복구
- 체크포인트(Checkpoint): 복구 속도를 향상시키기 위해 특정 시점의 데이터를 저장, 최신 데이터 상태를 저장하여 복구 속도를 향상
- Undo/Redo 기법: 트랜잭션이 완료되지 않았을 경우 원래 상태로 롤백(Undo), 완료된 경우 다시 적용(Redo)
- 예시: 온라인 쇼핑몰 결제
- 사용자가 신용카드 결제를 진행하여 결제가 승인됨
- 트랜잭션이 COMMIT된 이후 시스템 장애(정전, 서버 다운 등)가 발생하더라도, 결제 기록은 데이터베이스에 영구적으로 저장되어 있어야 함
- 데이터베이스는 트랜잭션 관리 기법(Log, Checkpoint, Undo/Redo) 등을 활용하여 복구 가능하도록 보장함
- 즉, 트랜잭션이 성공적으로 완료된 후에는 어떠한 시스템 장애가 발생하더라도 데이터가 손실되지 않음
트랜잭션 격리 수준(Isolation Level)
트랜잭션 격리 수준(Isolation Level)이란 동시에 여러 트랜잭션이 수행될 때, 데이터 정합성을 유지하면서 성능을 조절하는 방법을 의미한다. 격리 수준이 낮을수록 동시성이 높아지지만 데이터 충돌 가능성이 커지고, 격리 수준이 높을수록 충돌은 방지되지만 성능이 저하될 수 있다.
트랜잭션이 적절한 격리 수준을 선택하지 않으면, 아래과 같은 문제점이 발생할 수 있다.
- Dirty Read: 하나의 트랜잭션이 아직 커밋되지 않은 데이터를 읽음
- Non-Repeatable Read: 동일한 데이터를 여러 번 읽을 때, 중간에 다른 트랜잭션이 데이터를 변경하여 결과가 달라지는 현상
- Phantom Read: 동일한 쿼리를 실행했을 때, 중간에 다른 트랜잭션이 새로운 데이터를 삽입하여 결과 집합이 달라지는 현상
격리 수준의 종류
| 격리 수준 | 설명 | 해결되는 문제 | 데이터 정합성 | 동시성 |
| READ UNCOMMITTED | 커밋되지 않은 데이터를 읽을 수 있음 | 없음 | 낮음 | 높음 |
| READ COMMITTED | 커밋된 데이터만 읽을 수 있음 | Dirty Read 해결 | 중간 | 중간 |
| REPEATABLE READ | 트랜잭션이 실행되는 동안 동일한 데이터를 여러 번 읽어도 일관성 유지 | Dirty Read, Non-Repeatable Read 해결 | 높음 | 낮음 |
| SERIALIZABLE | 가장 높은 격리 수준으로, 트랜잭션을 직렬화하여 실행 | Dirty Read, Non-Repeatable Read, Phantom Read 해결 | 매우 높음 | 매우 낮음 |
트랜잭션과 데이터베이스 락(Lock)
데이터베이스는 여러 트랜잭션이 동시에 같은 데이터를 수정하려는 경우, 데이터 무결성을 보장하기 위해 락(Lock) 메커니즘을 사용한다. 락을 통해 데이터에 대한 접근을 제어하고 충돌을 방지할 수 있다. 공유 락(S-Lock)과 배타 락(X-Lock)은 트랜잭션이 자원을 안전하게 사용하도록 하는 메커니즘이다. 이러한 락 메커니즘을 적절히 활용하면 데이터 정합성을 유지하면서도 동시성을 최적화할 수 있다.
공유 락(Shared Lock, S-Lock)
- 공유 락은 데이터를 읽기(Read)할 때 사용되며, 여러 트랜잭션이 동시에 같은 데이터에 대해 공유 락을 설정할 수 있다.
- 하지만 공유 락이 걸려 있는 데이터에 대해 쓰기(Write) 연산은 불가능하다.
- 예시: 여러 사용자가 동일한 상품 정보를 읽을 수 있지만, 동시에 업데이트하는 것은 불가능함
배타 락(Exclusive Lock, X-Lock)
- 배타 락은 데이터를 수정(Write)할 때 사용되며, 다른 트랜잭션이 해당 데이터에 접근할 수 없도록 차단한다.
- 배타 락이 걸린 데이터는 읽기(Read)와 쓰기(Write) 모두 불가능하다.
- 예시: 한 사용자가 상품 정보를 수정하는 동안, 다른 사용자는 해당 상품 정보를 조회할 수 없음
공유 락(S-Lock) 과 배타 락(X-Lock) 은 트랜잭션 간 데이터 충돌을 방지하는 역할을 하지만, 오히려 데드락(Deadlock)을 초래할 수도 있다. 예를 들어, 아래와 같이 두 개의 트랜잭션이 서로 다른 테이블에 배타 락을 걸고 상대방이 소유한 락이 해제되기를 기다리면 데드락이 발생할 수 있다.
- 트랜잭션 A: user_table에 X-Lock → order_table에 X-Lock을 걸려고 함 (대기 중)
- 트랜잭션 B: order_table에 X-Lock → user_table에 X-Lock을 걸려고 함 (대기 중)
- 두 트랜잭션이 서로 상대방이 락을 해제하기를 기다림 → 데드락 발생!
데드락(Deadlock)
데드락(Deadlock)이란 두 개 이상의 트랜잭션이 서로 상대방의 락이 해제되기를 기다리면서 영원히 대기하는 상태를 의미한다. 여러 트랜잭션이 동시에 같은 자원을 접근하려고 하면, 서로 기다리면서 이러한 데드락이 발생할 수 있다. 이를 방지하기 위해 아래와 같은 방법을 사용할 수 있다.
해결 방법
1) 타임아웃 설정
- 특정 시간이 지나면 트랜잭션을 강제로 롤백하여 교착 상태를 해소함
- 예시: 트랜잭션이 일정 시간 동안 진행되지 않으면 자동으로 취소됨
2) 트랜잭션 우선순위 지정
- 시스템에서 트랜잭션의 중요도를 설정하여 우선순위가 낮은 트랜잭션을 취소(Rollback)하고 높은 우선순위 트랜잭션을 진행하게 함
- 예시: 중요한 결제 트랜잭션이 먼저 실행되고, 덜 중요한 데이터 갱신 트랜잭션은 롤백됨
3) 락 순서 지정
- 트랜잭션이 특정 순서로 락을 획득하도록 강제하여 데드락 발생 가능성을 줄임
- 예시: 모든 트랜잭션이 먼저 A 테이블을 락한 후, B 테이블을 락하도록 설정
4) 낙관적 락(Optimistic Lock) 활용
- 데이터를 수정하기 전, 먼저 읽고 나중에 변경 사항을 적용하는 방식으로 충돌 가능성을 줄임
- 예시: 마지막 수정 시간을 비교하여 변경 사항이 충돌하면 롤백하거나 재시도함
* [참고] "낙관적 락/비관적 락" 과 " 공유 락/ 배타 락" 의 차이
같은 락에 대해 설명하여 헷갈릴 수 있지만 결론부터 말하면 서로 다른 범주에 속하는 개념이다.
공유 락과 배타 락은 데이터베이스 시스템이 내부적으로 트랜잭션 간 충돌을 방지하기 위해 사용하는 락이다.
낙관적 락과 비관적 락은 애플리케이션 레벨에서 동시성을 제어하는 전략이다. 즉, 개발자가 데이터 무결성을 유지하기 위해 명시적으로 적용하는 방법이다.
[JPA] 동시성 제어 - 낙관적 락(Optimistic Lock), 비관적 락(Pessimistic Lock)
낙관적 락 예외..?Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.postdm.backend.domain.member.domain.entity.Member#1] 프로젝트에서 테스트 코드를 작성하던 중, ObjectOptimisticLockingFa
rimeir24.tistory.com
'Backend > DB' 카테고리의 다른 글
| SQL Mapper(MyBatis) vs ORM(JPA) (0) | 2025.03.16 |
|---|