1. 개요
이번에는 DB 트랜잭션과 Lock에 대해 정리하려고 한다. DB는 여러 사용자들이 동시에 데이터를 사용하도록 설계되었지만, 동시에 사용하면서 하나의 데이터에 동시에 여러 사용자가 접근하다보니, 데이터 정합성이나 불일치 문제가 발생할 수 있다. 이런 문제를 해결하기 위해서 트랜잭션과 Lock에 대한 개념을 공부할 필요가 있다.
2. 트랜잭션이란?
트랜잭션은 하나의 작업 단위이다.
예를 들어, A가 B에게 계좌 이체를 하는 경우 A 계좌에서 출금하고, B계좌에 입금된다. 만약 A 계좌에서 출금은 성공했으나, B계좌에 입금이 실패했을 경우 A계좌 출금을 롤백해줘야 한다. 이 때 사용하는 것이 트랜잭션이다.
a. 트랜잭션 특징은 ACID를 따른다.
- 원자성(Atomicity): 트랜잭션에 포함된 모든 작업은 모두 수행되거나, 전혀 수행되지 않아야한다.
- 일관성(Consistency): 트랜잭션 완료 후 일관성 있는 데이터를 유지 해야한다.
- 격리성(Isolation): 트랜잭션들은 서로 독립적으로 수행되어야 한다.
- 지속성(Durability): 트랜잭션이 성공하고, 결과는 영구적으로 반영되어야 한다.
3. 트랜잭션 격리 수준
또 다른 예시를 보자.
예를 들어, A와 B가 동시에 C에게 각각 1만원씩 입금한다고 가정하자.
C의 기존 잔액은 1만원인 상태이고, A, B의 입금 트랜잭션이 동시에 실행되는 상황이다.
정상적으로 처리된다면, C의 잔액은 3만원이 되겠지만, 격리성을 제대로 설정하지 않을 경우 2만원으로 업데이트 되어 1만원이 손실되는 경우가 발생할 수 있다.
a. 격리 수준 설정으로 인해 발생할 수 있는 문제
Dirty Read
- 한 트랜잭션이 커밋되지 않은 데이터를 읽었을 경우 발생한다.
- A 트랜잭션에서 잔액을 2만원으로 업데이트 했지만, 커밋은 하지 않았다.
- 이 순간 B가 C의 잔액을 조회하면 2만원으로 조회하는 문제가 발생한다.(트랜잭션 실패시 문제 발생)
Non-Repeatable Read
- 한 트랜잭션 내에서 두번 읽어 데이터 값이 달라지는 문제가 발생한다.
- B 트랜잭션에서 첫번째 조회했을 때는 1만원이였는데, A가 2만원으로 업데이트 한 후, B에서 다시 조회하면 2만원이 되어 있을 것이다.
Phantom Read
- 한 트랜잭션 내에서 같은 쿼리를 두번 수행했는데, 첫번째 쿼리에서 없던 Phantom(유령) 레코드가 조회되는 현상
- 첫번째 모든 입금 기록을 조회하는 쿼리에서 첫번째 조회시에는 1건이 있었는데, A 트랜잭션에서 입금 기록을 삽입하면서, 다시 조회할 때는 2건으로 조회되는 문제이다.
b. 격리 수준
위 문제처럼, 데이터 충돌 문제를 예방하기 위해 격리 수준을 설정할 수 있다.
격리 수준은 4단계로 정의 된다.
Read Uncommitted
- 다른 트랜잭션이 작업중인 데이터를 읽을 수 있다.
- Dirty Read 문제가 발생할 수 있어 거의 사용하지 않는다.
Read Committed
- 다른 트랜잭션이 커밋한 데이터만 읽을 수 있다.
- Dirty Read 문제는 발생할 수 있지만, 여전히 Non-Repeatable Read 문제가 발생할 수 있다.
Repeatable Read
- 같은 데이터를 읽을 때 항상 동일한 값을 읽을 수 있다.
- 안전하지만, Phantom Read 문제가 발생할 수 있다.
Serializeable Read
- 모든 트랜잭션을 순차적으로 실행해 완벽한 격리성을 보장한다.
- 가장 안전하지만, 처리 속도가 느려진다.
4. 격리 수준 설정
격리 수준은 DB 자체와 **DB 클라이언트(애플리케이션)**에서 모두 설정할 수 있다. DB는 PostgreSQL 기준으로 DB 클라이언트(애플리케이션)는 스프링 프레임워크에서 설정하는 방법에 알아보겠다.
a. DB Default 격리 수준 설정
DB는 기본적으로 자체 격리 수준을 제공한다. PostgreSQL의 경우 기본 격리 수준을 READ COMMITTED이다.
격리 수준을 확인하는 방법과 변경하는 방법을 알아보자.
격리 수준 확인
--- 격리 수준 확인
SHOW default_transaction_isolation;
격리 수준 설정
- 'SET default_transaction_isolation' 명령어로 격리 수준을 설정할 수 있다.
- DB 세션 단위로 적용된다. => 새로 연결된 세션은 영향을 주지 않음.
--- 격리 수준 설정
SET default_transaction_isolation = 'READ UNCOMMITTED';
SET default_transaction_isolation = 'READ COMMITTED';
SET default_transaction_isolation = 'REPEATABLE READ';
SET default_transaction_isolation = 'SERIALIZABLE';
b. 애플리케이션(DB 클라이언트)에서의 격리 수준 설정
JDBC를 통해 직접 격리 수준을 설정하지만, 요즘 JDBC를 직접 사용하는 경우는 거의 없으니 이 방법은 넘어가겠다.
스프링 프레임워크로 개발할 때 @Transactionl 어노테이션을 클래스(또는 메서드) 레벨에 붙혀서 사용하는 경우가 많을 것이다. @Transactional 어노테이션 내부에 isolation 속성을 통해 격리 수준을 설정할 수 있다.
isolation 속성을 보면, default가 Isolation.DEFAULT로 설정되어 있다. DEFAULT의 의미는 DB 플랫폼 설정을 따라가겠다는 의미이다. 즉 DB가 'READ COMMITTED'이면 그대로 사용하겠다는 의미이다.
격리 수준 설정
@Transactional(isolation = Isolation.DEFAULT) // Default
@Transactional(isolation = Isolation.READ_UNCOMMITTED) //READ_UNCOMMITTED
@Transactional(isolation = Isolation.READ_COMMITTED) // READ_COMMITTED
@Transactional(isolation = Isolation.REPEATABLE_READ) // REPEATABLE_READ
@Transactional(isolation = Isolation.SERIALIZABLE) // SERIALIZABLE
5. 마무리
마지막으로 정리하자면, 데이터 불일치 문제를 해결하기 위해서는 DB에서 트랜잭션으로 작업단위를 나누어서 처리한다. 이를 통해, 작업 도중에 문제가 발생하더라도 롤백을 통해 데이터를 안전하게 복구할 수 있다.
또한, 데이터 충돌 문제를 해결하기 위해 트랜잭션 격리 수준을 적절히 설정하는 것이 중요하다. 격리 수준에는 성능과 데이터 안전성 간의 균형이 필요하다. => 데이터 일관성이 중요한 작업일수록 더 높은 격리 수준을 적용해 엄격하게 처리하도록 보장.
'Data Infra > Database' 카테고리의 다른 글
[PostgreSQL] postgreSQL 9.6 (data, log 경로 변경) (0) | 2024.09.05 |
---|---|
[PostgreSQL] dnf(yum)로 postgreSQL 9.6(구버전) 설치 (0) | 2024.09.04 |