Transaction
트랜잭션은 '쪼갤 수 없는 업무 처리의 최소 단위'로 데이터베이스에서 데이터를 안정적으로 처리하기 위한 작업의 단위를 의미합니다. 이는 일련의 연산들의 모임으로서, 트랜잭션 내의 모든 연산이 완료되어야 하며, 일부만 완료되는 상황은 허용되지 않습니다. 즉, 트랜잭션 내의 모든 연산이 성공적으로 완료되거나, 또는 어떠한 연산도 적용되지 않아야 합니다.
트랜잭션은 데이터베이스 시스템에서 병행 제어 및 회복 작업 시 처리되는 작업의 논리적인 단위로 사용자가 시스템에 대한 서비스 요구 시 시스템이 응답하기 위한 상태 변환 과정의 작업 단위이다.
하나의 트랜잭션에는 하나 이상의 SQL 문장이 포함됩니다.
트랜잭션의 특징 - ACID
원자성 (Atomicity)
원자성(atomicity)은 하나의 트랜잭션이 더 이상 작게 쪼갤 수 없는 최소한의 업무 단위이다.
트랜잭션이 데이터베이스에 모두 반영되던지, 아니면 전혀 반영되지 않아야 하며 작업이 부분적으로 실행되거나 중단되지 않는 것을 보장하는 것으로 즉, All or Nothing의 개념으로서 작업 단위를 일부분만 실행하지 않는다는 것을 의미합니다.
100개 명령어로 구성된 트랜잭션 중 99개 완료 1개 실패가 된다면,
이는 무조건 실패로 간주하여 트랜잭션 시작 전 상태로 돌려야 합니다.
또한 100개가 모두 성공했을 시 트랜잭션은 성공이기 때문에 중간 상태가 없습니다.
트랜잭션은 사람이 설계한 논리적인 작업 단위이기 때문에 일처리가 작업 단위 별로 이루어져야 사람이 다루는데 무리가 없으며 만약 트랜잭션 단위로 데이터가 처리되지 않는다면, 설계한 사람은 데이터 처리 시스템을 이해하기 힘들 뿐만 아니라, 오작동 했을 시 원인을 찾기가 매우 힘들어집니다.
트랜잭션 내의 모든 명령은 반드시 완벽하게 수행되어야 하며, 모두가 완벽히 수행되지 않고 어느 하나라도 오류가 발생하면 트랜잭션 전부가 취소되어야 합니다.
트랜잭션이 원자성이라는 성질을 지니게 된 이유는 중간에 끊기게 되면 이후 해당 트랜잭션의 어디서부터 이어서 수행되어야 하는지 모르기 때문입니다.
일관성 (Consistency)
트랜잭션을 통해 데이터베이스의 상태가 변하더라도, 해당 데이터베이스는 트랜잭션 시작 전과 후에 모두 일관성 있어야 합니다.
시스템이 가지고 있는 고정요소는 수행 전과 후의 상태가 같아야 하며 트랜잭션의 작업 처리 결과가 항상 일관성이 있어야 한다는 것으로 트랜잭션이 진행되는 동안 데이터베이스가 변경되더라도 업데이트된 데이터베이스로 트랜잭션이 진행되는 것이 아니라, 처음 트랜잭션을 진행하기 위해 참조한 데이터베이스로 진행됩니다.
이렇게 함으로써 각 사용자가 일관성 있는 데이터를 볼 수 있고 트랜잭션 수행 전후의 데이터베이스 상태는 각각 일관성이 보장되는 서로 다른 상태가 됩니다.
트랜잭션 수행이 보존해야 할 일관성은 기본 키, 외래 키 제약과 같은 명시적인 무결성 제약 조건들뿐만 아니라, A에서 B로 돈을 이체할 때 A와 B 계좌의 돈의 총합이 같아야 한다는 사항과 같은 비명시적인 일관성 조건들도 있다.
독립성 또는 고립 / 격리성 (Isolation)
각 트랜잭션은 서로 독립적으로 실행되어야 합니다. 트랜잭션 수행 시 다른 트랜잭션의 연산이 끼어들지 못하게 해야합니다.
트랜잭션 끼리는 서로를 간섭할 수 없고 트랜잭션이 실행하는 도중에 변경한 데이터는 이 트랜잭션이 완료될 때까지 다른 트랜잭션이 참조하지 못하게 하는 특성입니다.
데이터베이스는 클라이언트들이 같은 데이터를 공유하는 것이 목적이므로 여러 트랜잭션이 동시에 수행되어야 한며 이때 트랜잭션은 상호 간의 존재를 모르고 독립적으로 수행되어야 한합니다.
독립성은 고립 / 격리성이라고도 하는데 이를 유지하기 위해서는 여러 트랜잭션이 동시에 접근하는 데이터에 대한 제어가 필요고 여러 트랜잭션이 동시에 수행되더라도 각각의 트랜잭션은 다른 트랜잭션의 수행에 영향을 받지 않고 독립적으로 수행되어야 합니다.
한 트랜잭션에서 데이터베이스를 변경한 내용은 트랜잭션이 커밋되기 전까지는 다른 어떤 질의나 트랜잭션과도 고립되어야만 한며 각 트랜잭션은 시스템 내에서 동시에 수행되고 있는 다른 트랜잭션들을 알지 못합니다.
한 트랜잭션의 중간 결과가 다른 트랜잭션에게는 숨겨져야 한다는 의미인데, 이러한 성질이 보장되지 않으면 트랜잭션이 원래 상태로 되돌아갈 수 없게 됩니다.
DBMS의 병행 제어 모듈이 트랜잭션의 고립성을 보장하며 예를 들어 하나의 트랜잭션이 A라는 계좌에서 작업을 하고 있을 경우 다른 트랜잭션이 A계좌에 대해 참조하거나 관여할 수 없고 작업이 끝날 때까지 대기해야 하는 것을 뜻합니다.
지속성 (Durability)
트랜잭션이 성공적으로 완료되면, 그 결과는 영구적으로 반영되어야 합니다.
시스템이 중단되더라도 완료된 트랜잭션의 결과는 그대로 유지되어야 합니다.
지속성(durability)은 트랜잭션의 성공 결과 값은 장애 발생 후에도 변함없이 보관되어야 한다는 것으로 트랜잭션이 정상적으로 완료된 경우에는 버퍼의 내용을 하드디스크(데이터베이스)에 확실히 기록해야 하며, 부분 완료(Partial Commit)된 경우에는 작업을 취소(Aborted)하여야 합니다.
즉, 정상적으로 완료 혹은 부분 완료된 데이터는 DBMS가 책임지고 데이터베이스에 기록하는 성질이 지속성이며 영속성이라고 표현하기도 합니다.
커밋(Commit)과 롤백(Rollback)
"커밋(Commit)"과 "롤백(Rollback)"은 트랜잭션을 처리하는 중요한 두 가지 작업입니다.
커밋(Commit)
트랜잭션 내의 모든 변경사항이 성공적으로 데이터베이스에 적용되면, 트랜잭션은 "커밋"됩니다.
커밋이 수행되면, 트랜잭션에 의해 수행된 모든 변경사항은 데이터베이스에 영구적으로 저장되며,
이후에는 롤백이 불가능합니다. 커밋은 트랜잭션이 성공적으로 완료되었음을 의미합니다.
롤백(Rollback)
트랜잭션이 실패하거나, 트랜잭션 처리 도중 문제가 발생하면, "롤백"이 발생합니다.
롤백이 수행되면, 트랜잭션에 의해 수행된 모든 변경사항이 취소되고,
트랜잭션 이전의 상태로 데이터베이스가 되돌려집니다.
이 두 가지 작업은 트랜잭션의 "원자성"과 "일관성"을 보장하는 데 중요한 역할을 합니다.
원자성은 트랜잭션이 '전부' 또는 '없음'의 상태를 가져야 함을 의미하며, 일관성은 트랜잭션 후에도 데이터베이스의 상태가 일관되어야 함을 의미합니다.
따라서 트랜잭션이 문제를 일으키면 롤백을 통해 트랜잭션 이전의 상태로 돌아갈 수 있어야 합니다.
트랜잭션이 경쟁하면 생기는 문제
데이터베이스에서 여러 트랜잭션들이 동시에 수행될 때, 트랜잭션들 간의 경쟁 상태가 발생하면 다음과 같은 문제들이 생길 수 있습니다.
Dirty Read
한 트랜잭션(T1)이 변경한 값을 다른 트랜잭션(T2)이 읽는데,
T1이 롤백되는 경우 T2가 "더러운" 즉, 잘못된 값을 읽게 됩니다.
Non-repeatable Read
한 트랜잭션이 같은 데이터를 두 번 읽는 동안, 다른 트랜잭션에 의해 그 데이터가 변경되는 경우를 말합니다.
이로 인해 같은 트랜잭션 내에서 같은 쿼리가 두 번 수행될 때 서로 다른 결과를 가져올 수 있습니다.
Phantom Read
한 트랜잭션 동안 같은 쿼리를 두 번 실행할 때, 첫 번째 쿼리에서는 없었던 새로운 레코드가 두 번째 쿼리에서 나타나는 현상입니다. 이는 다른 트랜잭션에 의해 새로운 레코드가 삽입되었기 때문입니다.
Lost Update
두 트랜잭션이 거의 동시에 같은 데이터를 수정하려고 할 때 발생하는 현상으로, 한 트랜잭션의 수정이 다른 트랜잭션의 수정에 의해 덮어씌워져 손실될 수 있습니다.
이와 같은 문제들을 해결하기 위해, 데이터베이스 관리 시스템(DBMS)은 보통 '트랜잭션 격리 수준(Transaction Isolation Level)'을 제공합니다. 트랜잭션 격리 수준은 트랜잭션에서 일관성 있는 데이터를 읽는 동안 발생할 수 있는 문제를 해결하는 방법을 정의합니다.
트랜잭션 격리 수준(Transaction Isolation Level)
"트랜잭션 격리 수준(Transaction Isolation Level)"이란 동시에 여러 트랜잭션이 실행되는 상황에서, 한 트랜잭션이 다른 트랜잭션에서 변경하거나 선택한 데이터를 얼마나 "보게" 될지를 결정하는 것입니다. 이는 동시성을 제어하고, 다음과 같은 문제들을 방지하는 데 사용됩니다.
트랜잭션 격리 수준에는 다음과 같은 4가지 수준이 있습니다.
READ UNCOMMITTED
트랜잭션은 커밋되지 않은 데이터를 읽을 수 있습니다.
이는 Dirty Read, Non-repeatable Read, Phantom Read를 허용합니다.
READ COMMITTED
트랜잭션은 오직 커밋된 데이터만 읽을 수 있습니다.
이는 Non-repeatable Read와 Phantom Read를 방지하지만, Dirty Read는 허용합니다.
REPEATABLE READ
한 트랜잭션이 데이터를 읽으면, 그 트랜잭션이 끝날 때까지 다른 트랜잭션은 그 데이터를 변경할 수 없습니다.
이는 Dirty Read와 Non-repeatable Read를 방지하지만, Phantom Read는 허용합니다.
SERIALIZABLE
모든 트랜잭션은 순차적으로 실행되어야 합니다.
이는 Dirty Read, Non-repeatable Read, Phantom Read를 모두 방지합니다.
격리 수준을 높이면 데이터의 일관성은 더 잘 보장되지만, 동시성이 감소하여 성능이 저하될 수 있습니다. 따라서 트랜잭션 격리 수준은 데이터의 일관성 요구사항과 성능 사이의 균형을 맞추는 데 중요한 역할을 합니다.
Spring 트랜잭션
Spring 프레임워크는 트랜잭션 관리를 단순화하고 일관된 방식으로 제공하는 많은 기능을 포함하고 있습니다.
Spring의 트랜잭션 관리는 일반적으로 두 가지 방법으로 수행됩니다.
프로그래밍 방식의 트랜잭션 관리
이 방식은 개발자가 트랜잭션 API를 직접 제어합니다. 이는 트랜잭션 관리를 위한 코드를 작성해야 하므로 복잡성이 증가하고 유지 관리가 어려울 수 있습니다.
선언적인 트랜잭션 관리
이 방식은 스프링의 AOP(Aspect Oriented Programming)를 활용합니다.
이 방식은 개발자가 비즈니스 로직에만 집중할 수 있게 하며, 트랜잭션 관리는 스프링이 알아서 처리합니다.
이 방식은 @Transactional 어노테이션을 사용해 메소드나 클래스에 적용하며, 이 어노테이션에는 여러 속성이 있습니다.
propagation 속성은 트랜잭션이 전파되는 방법을 정의하고, isolation 속성은 트랜잭션 격리 수준을 정의합니다. readOnly 속성은 트랜잭션이 읽기 전용인지를 나타내며, timeout 속성은 트랜잭션의 타임아웃을 설정합니다. 마지막으로, rollbackFor와 noRollbackFor 속성은 트랜잭션이 어떤 예외에 대해 롤백을 수행할지를 정의합니다.
스프링의 트랜잭션 관리는 플랫폼에 중립적이며, JDBC, Hibernate, JPA 등 다양한 데이터 액세스 기술을 지원합니다. 이는 다양한 데이터 액세스 기술에 대해 일관된 프로그래밍 모델을 제공함으로써, 개발자가 데이터 액세스 기술에 특화된 트랜잭션 API를 직접 다루는 데 필요한 복잡성을 크게 줄여줍니다.
@Transactional
@Transactional 어노테이션은 Spring에서 제공하는 선언적 트랜잭션 관리의 핵심 요소입니다.
선언적 트랜잭션 관리는 트랜잭션 관련 코드를 비즈니스 로직에서 분리하여 트랜잭션 처리를 단순화하는 방법입니다.
@Transactional을 메소드 또는 클래스에 추가하면, Spring은 해당 메소드 또는 클래스의 실행을 트랜잭션 범위 내에서 처리하게 됩니다. 메소드가 호출될 때 트랜잭션이 시작되고, 메소드가 정상적으로 완료되면 트랜잭션은 커밋되며, 메소드 실행 중 예외가 발생하면 트랜잭션은 자동으로 롤백됩니다.
@Transactional 어노테이션의 주요 속성들에 대한 설정값은 다음과 같습니다:
propagation - 트랜잭션 전파 옵션을 설정합니다. 이 속성의 가능한 값들은 Propagation 열거형의 멤버들입니다:
- REQUIRED
기본값입니다. 이미 존재하는 트랜잭션이 있으면 그것을 사용하고, 그렇지 않으면 새로운 트랜잭션을 시작합니다.
- SUPPORTS
이미 존재하는 트랜잭션이 있으면 그것을 사용하고, 그렇지 않으면 트랜잭션 없이 실행됩니다.
- MANDATORY
이미 존재하는 트랜잭션을 사용하고, 그렇지 않으면 예외를 발생시킵니다.
- REQUIRES_NEW
항상 새로운 트랜잭션을 시작하고, 이미 존재하는 트랜잭션 (있는 경우)는 일시 중단됩니다.
- NOT_SUPPORTED
트랜잭션 없이 메소드를 실행하고, 이미 존재하는 트랜잭션 (있는 경우)는 일시 중단됩니다.
- NEVER
트랜잭션 없이 메소드를 실행하고, 이미 존재하는 트랜잭션이 있으면 예외를 발생시킵니다.
- NESTED
이미 존재하는 트랜잭션이 있으면 중첩 트랜잭션을 시작하고, 그렇지 않으면 REQUIRED와 같은 방식으로 작동합니다.
isolation - 트랜잭션 격리 수준을 설정합니다. 이 속성의 가능한 값들은 Isolation 열거형의 멤버들입니다:
- READ_UNCOMMITTED: 트랜잭션은 아직 커밋되지 않은 데이터를 읽을 수 있습니다.
- READ_COMMITTED: 트랜잭션은 오직 커밋된 데이터만 읽을 수 있습니다. 이는 기본값입니다.
- REPEATABLE_READ: 트랜잭션 동안 같은 레코드를 여러 번 읽을 때 항상 동일한 결과를 보장합니다.
- SERIALIZABLE: 모든 트랜잭션을 순차적으로 실행하여, 한 번에 오직 하나의 트랜잭션이 데이터에 접근하도록 합니다.
readOnly - 트랜잭션을 읽기 전용으로 설정합니다. 이는 성능 최적화에 도움이 될 수 있습니다. 기본값은 false입니다.
timeout - 트랜잭션의 타임아웃을 초 단위로 설정합니다. 0보다 큰 값으로 설정되면, 해당 시간이 초과되면 시스템은 트랜잭션을 롤백합니다. 기본값은 -1로, 이는 타임아웃이 없음을 의미합니다.
rollbackFor, noRollbackFor - 트랜잭션 롤백을 어떤 예외에 대해 수행할 것인지, 수행하지 않을 것인지를 지정합니다. rollbackFor는 롤백을 수행할 예외를 지정하고, noRollbackFor는 롤백을 수행하지 않을 예외를 지정합니다. 각 속성의 값은 예외 클래스의 배열입니다.
'BACKEND > SPRING' 카테고리의 다른 글
05. API를 작성하는 다양한 방법 - GET API 만들기 (0) | 2023.07.30 |
---|---|
04. 스프링 부트 애플리케이션 개발하기 - 프로젝트 생성과 Hello World 출력하기 (1) | 2023.07.28 |
03. 개발 환경 구성 - JDK 및 IDE 설치 (0) | 2023.07.23 |
02. 개발에 앞서 알면 좋은 기초 지식 - REST API (0) | 2023.07.22 |
02. 개발에 앞서 알면 좋은 기초 지식 - 레이어드 아키텍처 및 디자인 패턴 (0) | 2023.07.22 |