이론 정리/Database

트랜잭션의 격리수준과 문제점, 그리고 해결법

철매존 2022. 9. 7. 22:08
728x90

트랜잭션의 격리수준

동시에 여러 트랜잭션을 처리할 때에, 얼마나 이들이 서로 고립되어 있는지를 의미한다.
즉, A라는 트랜잭션이 B라는 트랜잭션 내에서 변경한 데이터를 보는 기준점을 결정한다.

격리수준의 종류

이는 4가지로 분류할 수 있는데,

  • READ UNCOMMITTED
    • 커밋되지 않은 데이터를 읽을 수 있음
  • READ COMMITTED
    • 커밋된 데이터만 읽을 수 있음
  • REPETABLE READ
    • 트랜잭션 동안 같은 데이터를 읽을 수 있음
  • SERIALIZABLE
    • 트랜잭션의 순차적 실행

이다.
한번 하나하나 살펴보자.

READ UNCOMMITTED

아직 커밋되지 않은 데이터를 읽을 수 있는 격리 수준이다.
즉 Transaction의 COMMIT이나 ROLLBACK과 상관 없이 해당 트랜잭션의 데이터를 확인 가능하다.

이 때에 당연히 문제가 생길 것이라고 예상할 수 있을 텐데, 위에서 볼수 있듯 COMMIT/ROLLBACK여부와도 상관없이 데이터를 바로 본다면 어떻게 변화할지 확신이 서지 않으므로 뭔가 믿음이 안갈 것이다.

Dirty Read problem

예시를 들어보자

사원 Table
- 이름
- 연봉
출금 Table
- 종류
- 금액

두 개의 DB에 대해서 두개의 트랜잭션이 동시에 돌아간다고 해보자

A : 출금 Table에서 모든 사원들의 연봉을 더해서 연봉이라는 종류를 갖는 금액을 구하려 한다.
B : 올해 사원들이 입사했다. 각 사원들의 이름과 연봉을 넣어준다.

그리고 여기서 READ UNCOMMITTED 격리 수준을 사용한다면?

이런 식으로 신규입사를 진행했다고 생각하면 신규입사자가 확정되기 전에 이 친구들의 연봉을 미리 B트랜잭션에서 계산할 것이다.

근데 만약에 여기서 당사 내부 사정으로 인해 부득이하게 채용이 취소되면 어떻게 될까?

이제 남궁찬과 황보찬은 입사가 취소되었다.
당연히 사원 Table에도 들어가지 않고, 트랜잭션이 Rollback되었을 것이다.

그런데 B트랜잭션은 이미 계산을 해놓은 상태이기 때문에 실제 연봉 지급액보다 계산 금액이 더 큰 문제가 발생한다!!!

이런 문제를 바로 Dirty Read문제라고 한다.


이러한 문제때문에 READ UNCOMMITTED의 사용은 지양되고 있다.

READ COMMITTED

딱 보면 알겠지만 COMMITTED된 데이터만을 읽어오는 친구이다.
위의 문제를 해결해준 친구이고, Oracle DB에서 표준으로 사용함과 동시에 대부분의 경우 사용하는 격리 수준이다.

위에서 발생하는 Dirty Read문제가 발생하지 않는다.
이는 커밋이 완료된 데이터만 타 트랜잭션에서 조회 가능하기 때문이다.

근데 이러면 뭔가 이상한 점이 있을 것이다.

NON-REPEATABLE Read problem

위와 동일하게, 입사자에 대해 사용하는 금액을 구하는 경우가 있다고 가정해 보자

먼저, 남궁찬과 황보찬의 데이터를 DB에 넣는 A트랜잭션이 아직 진행중인 상태이다.

트랜잭션이 진행중인 관계로 이들의 데이터는 DB에는 입력되지 않았다.
남궁찬황보찬의 데이터는 별도의 영역에 저장되며, 이를 UNDO영역이라고 한다.
말 그대로 아직 트랜잭션이 완료되지 않은 상태의 영역에 저장된다고 한다.
당연하지만 undate나 delete또한 undo영역에 들어간다.

그리고 B트랜잭션은 아직 입력되지 않은 두명의 데이터를 제외하고, 류찬, 김찬, 박찬의 연봉을 합쳐서 구한다.

이후로 남궁찬과 황보찬의 입사가 완료되었다.
그런데 총 연봉은 이 둘의 데이터가 commit되기 전에 구했기 때문에 undo 영역에 있던 둘의 연봉이 맞지 않는다.
추후 다시 B트랜잭션을 진행한다면 모든 입사자의 연봉을 가져올 것이다.
이처럼 동일한 트랜잭션을 사용하는데 다른 결과를 가져오는 문제가 발생하는데, 이를 NON-REPEATABLE Read problem이라고 한다.

Repeatable Read

트랜잭션 동안 같은 데이터를 읽을 수 있는 격리 수준이다.

위에서 발생하던 NON-REPEATABLE Read Problem을 해당 격리 수준에서는 해결할 수 있다.

개념적으로 설명하자면, '트랜잭션의 UNDO영역에 있는 데이터를 보여 주는' 방식이다.
그러면 READ UNCOMMITTED랑 뭐가 다른건데? 라고 생각할 수 있는데, 사실 비슷하다.

차이점이 있다면 'UNDO영역의 몇 번째 버전의 데이터를 보여 주는지' 즉 트랜잭션의 번호에 따른 활용 데이터 적용이다.

간단하게 말하면 그냥 자기 트랜잭션 번호보다 낮은 트랜잭션 번호를 갖는 데이터를 볼 수 있다는 것이다.

순서대로 한번 보자

  1. 처음부터 있던 기존 입사자는 (1)의 번호를 부여받은 상태이다.
  2. A트랜잭션을 실행하였고, 여기서 신규 입사자들을 DB에 insert했다.
  3. 해당 입사자들은 UNDO영역에 (2)의 번호를 부여받아 들어가있다.
  4. B트랜잭션으로 연봉 계산을 하는데, UNDO영역에 있는 A트랜잭션에 있던 친구들은 (2)의 번호를 받아두었다.
  5. 그래서 이것이 B트랜잭션의 것보다 작은 트랜잭션 번호를 가지고 있으므로 해당 UNDO영역의 데이터를 볼 수 있다.

다만 이 경우 한가지 문제가 발생할 수 있는데, 이것이 바로 PHANTOM READ이다.

PHANTOM READ Problem

이름 그대로 유령마냥 내가 데이터를 입력하거나 지운 것도 아닌데, 이게 갑자기 튀어나오거나 없어져 버리는 문제이다.

만약에 누군가가 정신이 나가버려서 연봉을 두번 계산한다고 가정해 본다.

  1. 기존 입사자들의 데이터가 있음(1)
  2. B트랜잭션이 첫번째 검색을 수행중...
  3. A트랜잭션에 의해 신규입사자가 들어오는중이다 (INSERT단계) -> UNDO(3)
  4. 2번 단계에서 진행중인 B트랜잭션에서는 (3)의 숫자를 갖는 UNDO영역 데이터를 읽지 못한다.
  5. B트랜잭션이 두번째 검색을 수행중...
  6. 5번 단계에서 진행중인 B트랜잭션에서는 (3)의 숫자를 갖는 UNDO영역 데이터를 읽는다!!

여기서 보면 분명 B트랜잭션은 동일한 검색을 동시에 하나의 트랜잭션에서 수행했다.
근데 값이 다르다....

이렇게 자신이 변경한 데이터가 없는데도 데이터가 있다 없다 하는걸 PHANTOM READ라고 한다.

SERIALIZABLE

위의 모든 문제를 해결한 방식이다.

매우 간단한데, 이름 그대로 순서대로 접근하게 하는 것이다.
즉 C R U D 뭐든 상관없이, 어떤 트랜잭션에서 접근중인 데이터에는 다른 트랜잭션이 하나도 접근하지 못하는 것이다.

이렇게 되면 당연히 데이터가 어찌 바뀔 염려가 없으니 모든 문제에서 자유롭다.

그리고 이런 것들이 가지는 필연적인 문제로, 동시 처리가 불가능하다.
이거를 생각해보면 편할듯?

위의 글에서도 알 수 있듯, 이녀석은 동시처리가 불가능해서 성능이 굉장히 떨어진다. 그래서 사용이 권장되지 않는다.