[DB] 트랜잭션과 락 - 1. 트랜잭션 격리 수준

유알·2023년 3월 2일
0

[DB/JPA]

목록 보기
2/7
post-thumbnail

멀티쓰레드 환경에서 회원가입 서비스를 만들다가 트랜잭션에 엄밀한 정의와 이해가 필요하게 되어 공부를 시작하게 되었다.

트랜잭션이란?

트랜잭션은 분리할 수 없는 하나의 단위로 실행되는 데이터베이스 작업단위를 뜻한다.

트랜잭션의 목적은 트랜잭션 내의 모든 작업이 올바르게 실행되거나 전혀 실행되지 않도록 하는 것이다. 즉, 트랜잭션은 오류나 시스템 오류가 발생한 경우에도 데이터베이스가 일관된 상태를 유지하도록 보장합니다.

ACID 란?

트랜잭션은 ACID라고 불리는 Atomicity(원자성), Consistency(일관성), Isolation(격리성), Durability(지속성) 을 보장해야 한다.

1. Atomicity - 원자성

트랜잭션 내에서 실행한 작업들은 마치 하나의 작업처럼, 모두 실패하거나 모두 성공해야한다.

2. Consistency - 일관성

모든 트랜잭션은 일관성 있는 데이터베이스 상태를 유지해야한다.
예를 들어 데이터베이스에서 정한 무결성 제약조건은 항상 만족되어야 합니다.

3. Isolation - 격리성

동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리해야한다.
격리성은 동시성과 관련된 성능 이슈로 인해 격리 수준을 선택할 수 있다.

4. Durability - 지속성

트랜잭션을 성공적으로 끝내면 그 결과가 항상 기록되어야한다.
중간에 시스템에 문제가 발생해도 데이터베이스 로그 등을 사용해서 성공한 트랜잭션 내용을 복구해야한다.

트랜잭션 격리 수준

트랜잭션은 원자성, 일관성, 지속성을 보장한다.
문제는 격리성인데 만약 격리성을 완벽히 보장하려면, 트랜잭션을 차례대로 실행해야한다.
이렇게 되면 동시성 성능이 매우 나빠지게 된다.

이런 문제로 인해 ANSI 표준은 트랜잭션의 격리 수준을 4단계로 나누어 정의 했다.

  1. READ_UNCOMMITTED
  2. READ_COMMITTED
  3. REPEATABLE_READ
  4. SERIALIZABLE

SERIALIZABLE로 갈 수록 격리 수준은 높아지지만, 동시성 성능이 떨어지게 된다.
반대로 READ_UNCOMMITTED로 갈 수록 동시성 성능은 좋아지지만, 격리 수준은 낮아져 다양한 문제가 생기게 된다.

그러므로 아래에 나오는 문제를 고려하여 적절한 격리 수준을 선택해야한다.

이 표는 각 격리 수준에 따라 어떠한 문제가 생길 수 있는지 나타내고 있다.

격리 수준에 따른 문제점

1. READ_UNCOMMITTED - Dirty Read 발생

READ_UNCOMMITTED 격리 수준에서는 각 트랜잭션에서 수정한 내용이 ROLL BACK이나 COMMIT 여부에 상관없이 다른 트랜잭션에 보여지게 된다.
즉 다른 트랜잭션이 커밋하지 않은 데이터를 읽을 수 있다. (READ UNCOMMITTED)

예를 들어 트랜잭션 A와 트랜잭션 B가 있다고 하자.
A가 어떤 데이터를 수정하고 커밋하지 않고 있는데, B가 A가 수정한 데이터를 읽어 어떤 작업을 완료했다. 그리고 A가 이 데이터를 커밋하지 않고 롤백하였다면, 이는 데이터 정합성에 심각한 문제를 발생시킬 수 있다.
이를 Dirty Read 라고 한다.

Dirty Read : 커밋 안된 데이터를 읽을 수 있음.

2. READ_COMMITTED - Non-Repeatable Read 발생

커밋된 데이터만 읽을 수 있다. 따라서 Dirty Read가 발생하지 않다. 하지만 Non-Repeatable Read가 발생할 수 있다.

Non-Repeatable Read란 같은 트랜잭션 내에서 같은 행에 대한 데이터를 반복해서 조회했을 때 값이 바뀌는 것을 말한다.
예를 들어서 A가 어떤 회원을 조회했다. 그리고 B트랜잭션이 그 회원을 수정했다.
그리고 A가 다시한번 그 회원을 조회하게 되면, A입장에서는 변경된, 처음과 다른 값을 얻게 된다.

Dirty Read는 허용하지 않지만 Non-Repeatable Read는 허용하는 상태를 Read-Committed이다.

작동원리는 DB에 UNDO라는 영역이 있다. 만약 한 트랜잭션이 UPDATE나 DELETE같은 구문을 실행하면, 기존의 데이터는 UNDO라는 영역에 임시보관되게 된다.
이 영역은 롤백 시 활용되고, 또 READ_COMMITTED상태에서 다른 트랜잭션이 이 데이터에 접근할 시 COMMIT 되기 전이라면 UNDO의 값을 제공함으로써 커밋된 데이터만 제공할 수 있게 한다.

3. REPEATABLE_READ

이 격리 수준은 전의 READ_COMMITTED에서 Non-Repeatable Read를 해결하여, 트랜잭션내에서 반복해서 같은 데이터를 얻을 수 있게 한 것이다. 대신 Phantom Read 부정합은 계속해서 발생한다.

작동원리는 간단하다 UNDO 영역을 활용하는 것은 동일하지만, 일종의 버전 정보를 활용하는 것이다. 즉 자신이 부여받은 버전보다 낮은 버전만 조회할 수 있게 하는 것이다. 중간에 다른 트랜잭션이 정보를 수정하더라도, 그 수정된 데이터는 버전이 높으므로 조회하지 않고 자신에게 알맞은 버전의 백업 정보를 얻게 된다. (완벽한 설명은 아니다.)

Phantom Read는 Non-Repeatable Read와 비슷하지만 다르다. Phantom Read는 반복 조회시 결과 집합이 달라지는 것을 의미한다.
예를 들어 트랜잭션 A와 B가 있을 때, A가 10살 이하의 사람을 조회하고, 그 다음 B가 5살의 회원을 추가했을 때, A가 다시 동일한 10살 이하를 조회하면, 한명이 늘어난 결과가 조회 되는 것이다.

이렇게 같은 트랜잭션 내에서 같은 쿼리를 연속해서 실행했을 때 결과 집합이 달라지는 것을 Phantom read라고 한다.

추가로 말하자면 MySql의 InnoDB 스토리지 엔진의 기본 격리 수준이기도하다. 또한 InnoDB를 사용시 Phantom Read도 발생하지 않는다.

4. SERIALIZABLE

가장 엄격한 트랜잭션 격리 수준이다. 여기서는 Phantom Read가 발생하지 않는다. 하지만 동시성 처리 성능이 급격히 떨어질 수 있다.

격리수준의 선택

애플리케이션 대부분은 동시성 처리가 중요하므로 데이터 베이스들은 보통 READ COMMITTED 격리 수준을 기본으로 사용한다. 일부 중요한 비즈니스 로직에 더 높은 격리 수준이 필요하면 데이터 베이스 트랜잭션이 제공하는 잠금기능을 사용하면 된다.

profile
더 좋은 구조를 고민하는 개발자 입니다

0개의 댓글