티스토리 뷰
트랜잭션이 나올 때마다 단골처럼 나오는 예시가 은행 송금 상황이다. 예를들어 예전에 친구에게 만원을 빌렸다가 갚는 상황을 생각해보자. 은행 어플을 열고 친구의 계좌번호와 만원을 입력하고 송금 버튼을 눌러서 내 계좌에서 돈이 빠져나갔다. 하지만 어떤 이유에서인지 친구의 계좌에는 만원이 입금되지 않았다. 일반적인 생각에서는 송금에 실패했다면 다시 내 계좌로 만원이 되돌아오는게 정상적이다. 트랜잭션은 이러한 정상적인 상황을 보장하기 위한 개념이다.
트랜잭션이란, 하나의 작업 단위로 간주되는 일련의 연산 또는 명령 집합을 의미한다. 위 예시로 생각하자면 하나의 작업이라는 것은 `송금`이 될 것이고 명령의 집합은 `{출금, 입금}`이 될 것이다. 즉, 내 계좌에서 만원이 빠지고 친구 계좌에 만원이 입금되는 모든 과정이 하나의 트랜잭션이 되는 것이다.
트랜잭션의 특성 - ACID
트랜잭션의 일관성과 신뢰성을 보장하기 위해 주요 4가지 특성이 있다.
1. Atomicity(원자성)
원자성은 트랜잭션 내의 모든 명령이 완전히 수행되거나 전혀 수행되지 않아야 한다는 개념이다. 앞서 봤던 예시로 봤을 때, 송금이 정상적으로 이루어지지 않았다면 내 계좌에서 만원이 출금되지 않은 상태로 돌아와야하고 마찬가지로 친구 계좌에 만원이 입금되지 않은 상태가 되어야 한다.
2. Consistency(일관성)
일관성은 데이터베이스가 잘못된 상태에 빠지지 않도록 보장하는 개념이다. 즉, 트랜잭션의 실행 전과 실행 후 항상 유효한 상태를 유지해야 한다. 데이터베이스에는 `참조 무결성`, `도메인 무결성`, `고유성 제약 조건` 등의 여러 제약 조건이나 규칙들이 설정되어 있으며 이를 통해 트랜잭션의 실행 전과 실행 후의 데이터베이스 상태가 설정된 규칙을 만족하고 있는 상태여야 한다는 것이다. 이를 통해 데이터 오류를 방지하고 무결성을 유지한다.
3. Isolation(격리성)
격리성은 여러 트랜잭션이 실행되고 있을 때 서로 간섭하지 않아야 한다는 것으로 트랜잭션은 독립적으로 실행되어야 한다는 개념이다. 만약 격리성이 잘 지켜지지 않으면 여러 동시성 문제가 발생한다.
4. Durability(지속성)
지속성은 트랜잭션이 성공적으로 완료되면 그 결과는 영구적으로 데이터베이스에 반영되어야 한다는 것이다. 갑자기 시스템이 다운되더라도 커밋이 완료된 트랜잭션의 결과는 데이터베이스에 안전하게 저장되어 있어야 한다.
이러한 4가지의 특성이 모두 완벽하게 보장되면 좋겠지만 보장하기 어렵거나 보장하더라도 비효율적인 데이터베이스가 될 수도 있다. 그래서 일부 특성은 상황에 따라 조정한다. 대표적으로 데이터베이스는 트랜잭션의 격리 수준을 제공해 격리성을 조정할 수 있도록 해준다.
트랜잭션 동시성 문제
위의 트랜잭션의 특성에서 격리성이 잘 지켜지지 않으면 여러 동시성 문제가 발생한다. 격리성이 잘 지켜지지 않아 트랜잭션이 서로 간섭하게 되면 대표적으로 3가지의 문제들이 발생하게 된다.
1. Dirty Read
Dirty Read는 한 트랜잭션이 다른 트랜잭션이 아직 커밋하지 않은 데이터를 읽을 때 발생한다. 예를들어보자.
- 트랜잭션 A에서 닉네임이 ABC인 유저의 닉네임을 “DEF”로 변경(아직 commit되지 않은 상태)
- 트랜잭션 B에서 유저 닉네임 “DEF” 조회
트랜잭션 B는 트랜잭션 A에서 변경한 유저 닉네임 "DEF"를 조회했지만 아직 트랜잭션 A가 커밋되지 않았다. 즉, 트랜잭션 A가 성공하냐, 실패하냐에 따라 유저 닉네임의 변경 여부가 결정된다. 따라서 트랜잭션 B는 아직 커밋되지 않은 부정확한 데이터를 조회하게 된 것이다. 이러한 문제가 Dirty Read이다.
2. Non-repeatable Read
한 트랜잭션에서 동일한 데이터를 여러 번 읽을 때, 조회 결과가 달라지는 문제이다.
- 트랜잭션 A가 계좌 잔액 100만원을 조회
- 트랜잭션 B가 동일한 계좌에 20만원 입금 후 커밋
- 트랜잭션 A가 동일한 계좌 잔액 120만원 조회
트랜잭션의 격리성이 잘 지켜졌다면 트랜잭션 B가 트랜잭션 A가 수행되는 동안 값을 변경할 수 없다. 이렇게 한 트랜잭션에서 동일한 데이터를 여러 번 읽는 동안 다른 트랜잭션에 의해 조회 결과가 달라지는 문제가 Non-repeatable Read이다.
3. Phantom Read
Phatom Read는 동일한 트랜잭션 내에서 동일한 조건으로 여러 번 데이터를 조회할 때 그 결과가 달라지는 문제이다.
- 트랜잭션 A가 잔액이 150만원 이상인 계좌 수 조회 = 결과 2건
- 트랜잭션 B가 ID = 1인 계좌에 50만원 입금
- 트랜잭션 A가 잔액이 150만원 이상인 계좌 수 조회 = 결과 3건
이렇게 동일한 트랜잭션 내에서 동일한 조건으로 여러 번 데이터를 조회할 때 중간에 다른 트랜잭션이 새로운 데이터를 넣거나 또는 기존의 데이터를 수정해 조건을 만족하도록 만들어 여러 번의 조회 결과가 달라지는 문제가 Phantom Read이다.
트랜잭션 격리 수준
데이터베이스는 트랜잭션의 격리 수준을 조정해 위와 같은 문제들을 해결할 수 있도록 돕는다. 격리 수준에는 4가지 단계가 있다.
1. Read Uncommitted - 커밋되지 않은 읽기
다른 트랜잭션에서 수정했지만 아직 커밋되지 않은 상태의 데이터도 읽을 수 있는 가장 낮은 단계의 격리 수준이다. 커밋을 기다릴 필요 없이 바로바로 읽기 때문에 성능이 가장 뛰어나며 동시성 처리가 극대화 된다. 하지만 데이터 일관성에 매우 취약하고 격리성으로 발생할 수 있는 3가지 문제(Dirty Read, Non-repeatable Read, Phantom Read)가 모두 발생할 수 있다.
2. Read Committed - 커밋된 읽기
Read Uncommitted 수준에서 한 단계 높은 격리 수준으로 다른 트랜잭션이 커밋한 데이터만 읽을 수 있는 격리 수준이다. 때문에 Dirty Read 문제를 방지할 수 있어 어느정도의 일관성이 보장되며 성능 또한 준수하다. 하지만 여전히 다른 트랜잭션이 중간에 값을 수정하는 경우가 발생하면 Non-repeatable 문제가 발생할 수 있다.
3. Repeatable Read - 반복 읽기
트랜잭션이 한번 읽은 데이터는 동일한 트랜잭션 내에서 다시 한번 읽을 때 항상 같은 결과를 보장하는 격리 수준이다. Non-repeatable Read 문제를 방지해 동일한 트랜잭션 내에 데이터의 일관성이 유지된다. 하지만 다른 트랜잭션에서 데이터를 수행했을 때 팬텀 데이터가 조회될 수 있는 Phantom Read 문제가 발생할 수 있다.
많은 데이터베이스에서 Read Committed와 Repeatable Read의 구현을 MVCC(Multi-Version Concurrency Control, 다중 버전 동시 제어) 방식을 사용한다. MVCC는 데이터에 대해 여러 버전을 함께 유지한다. 덕분에 현재 진행하고 있는 트랜잭션 외의 다른 트랜잭션이 데이터를 읽으려할 때 현재 변경된 데이터가 아닌 가장 최근에 커밋된 버전의 데이터를 읽도록 한다.
이러한 방식을 사용하는 대표적인 데이터베이스가 MySQL InnoDB이다. InnoDB는 Repeatable Read 수준에서는 `MVCC + Next Key Lock`을 사용해 Non-repeatable 문제 뿐만아니라 Phantom Read 문제가 발생하지 않도록 보장하기도 한다.
4. Serializable - 직렬화
가장 엄격한 격리 수준으로 트랜잭션들의 결과가 순차적으로 진행되었을 때와 같음을 보장하는 격리 수준이다. 한 트랜잭션이 실행되는 동안 다른 트랜잭션이 변경되고 있는 데이터를 커밋이 되기 전까지 보지도 수정하지도 못하기 때문에 모든 동시성 문제를 방지할 수 있다. 하지만 트랜잭션 간에 엄격한 Lock이 적용되어 트랜잭션 간의 블로킹이 발생해 성능이 저하될 수 있다. 이러한 격리 수준은 금융 거래와 같이 데이터의 일관성이 매우 중요한 환경에 적합하다.
직렬화 격리 수준을 구현하는 방식에는 여러가지가 있다.
- Stric 2PL(Two-Phase Locking)
- Validation-Based Concurrency Control
- 비관적/낙관적 동시성 제어 기법(SSI: Serializable Snapshot Isolation)
- 등
'CS > Database' 카테고리의 다른 글
Database Concurrency Control (0) | 2024.11.05 |
---|---|
Database Lock (2) | 2024.10.24 |