Database/RDB

[Database] 트랜잭션 격리 수준이란?

kkang._.h00n 2024. 1. 12. 04:30

개요

DB에 관심을 갖고 테코톡과 Real MySQL을 읽으며, 트랜잭션과 동시성 처리 등에 관심을 갖게 되었다. 그 중 가장 어렵다고 느낀 트랜잭션 격리 수준에 대한 개념을 정리하려 한다.

 

트랜잭션 격리 수준은 4단계로 나누어지며, 각 단계 별 부정합 문제점들을 갖고 있어 뒤로 갈 수록 갖고 있던 문제점들을 해결해 나간다.

 

 

 

1. READ UNCOMMITTED

각 트랜잭션의 변경내용이 COMMIT 혹은 ROLLBACK 여부에 상관없이 다른 트랜잭션에 노출된다.

 

예시

  1. 사용자 A가 트랜잭션을 시작함과 동시에 emp_no = 50000 인 사원을 INSERT 후 COMMIT은 하지 않는다.
  2. 하지만 사용자 B가 테이블 SELECT 시emp_no = 50000 인 사원이 조회된다

트랜잭션이 COMMIT 되기도 전에 이미 데이터는 테이블에 들어갔다❗

다음과 같은 현상을 Dirty read 라고 한다.

 

Dirty read란?

어떤 트랜잭션에서 처리한 작업이 완료되지 않았지만, 다른 트랜잭션에서 볼 수 있는 현상

Dirty read가 해결되지 않으면, 트랜잭션 내에서 COMMIT 되지 않아도 데이터를 쓰고 지우는 모든 작업들이 테이블에 그대로 드러나게되어 혼란스럽게 될 것이다. 동시성은 가장 크지만, 데이터의 정합성이 크게 어긋나기에 해당 수준은 사용하지 않는다고 한다.

 

 

 

2. READ COMMITTED

어떤 트랜잭션에서 데이터를 변경했더라도 COMMIT된 데이터만 다른 트랜잭션에서 조회 가능

 

예시

  1. 사용자 Aemp_no = 50000인 사원의 first_name을 UPDATE 한다.
  2. 이전 값은 언두 영역으로 백업
  3. 사용자 A가 COMMIT 전, 사용자 Bemp_no = 50000인 사원 조회
  4. 사용자 B의 조회 결과는 언두 영역에 저장 된 데이터로 조회

COMMIT된 데이터만 조회 가능하기에, Dirty read 문제점은 해결할 수 있지만 다음과 같은 문제점이 있다.

 

문제점

  1. 사용자 B가 트랜잭션을 시작하고, first_name = 'Toto' 인 데이터를 조회하였지만 해당 데이터는 존재하지 않는다.
  2. 그 후 사용자 A가 바로 first_name = 'Toto' 인 데이터를 INSERT 후 COMMIT한다.
  3. 사용자 B가 트랜잭션 종료 전  first_name = 'Toto' 인 데이터를 조회하면, 해당 데이터는 존재한다.

사용자 B의 입장에서 보면, 한 트랜잭션 내에서 같은 조회 쿼리를 날렸지만 다른 결과가 반환 되었다❗

다음과 같은 현상을 NON-REPEATABLE READ 라고 한다.

 

NON-REPEATABLE READ 란?

하나의 트랜잭션 내에서 똑같은 SELECT 쿼리를 실행했을 때, 항상 같은 결과를 가져와야 하는 정합성(REPEATABLE READ)에 어긋나는 것

 

 

 

3. REPEATABLE READ

언두 영역에 백업된 이전 데이터를 이용해 동일 트랜잭션 내에서는 동일한 결과를 보여줄 수 있게 보장

각 트랜잭션에 번호가 존재하며, 언두 영역에 존재하는 백업 데이터 중, 데이터의 트랜잭션의 번호보다 낮은 버전을 조회한다. 

 

예시

  1. 사용자 A의 트랜잭션의 번호는 12, 사용자 B의 트랜잭션의 번호는 10
  2. 사용자 B10 트랜잭션을 시작하고, emp_no = 50000인 레코드를 조회한다. -> 'Lara' 조회
  3. 그 후 사용자 A12 트랜잭션에서 first_name = ‘Toto’로 변경 후 COMMIT한다.
  4. 사용자 B가 같은 10 트랜잭션 안에서 emp_no = 50000인 레코드를 조회한다. -> 언두 로그의 'Lara' 조회

다른 트랜잭션에 의해 테이블이 변경되어도, 언두 로그를 이용하여 변경되기 전 데이터를 조회하기에

NON-REPEATABLE READ는 해결할 수 있지만, 다음과 같은 문제점이 있다.

 

문제점

  1. 사용자 B가 'SELECT…FOR_UPDATE'를 통하여 emp_no ≥ 50000인 데이터를 조회한다 -> 결과 1건 조회, 해당 레코드 Lock
  2. 사용자 A새로운 데이터를 INSERT 후 COMMIT 한다.
  3. 사용자 B가 'SELECT…FOR UPDATE'를 통하여 emp_no ≥ 50000인 데이터를 조회한다 -> 결과 2건 조회

다른 트랜잭션에서 수행한 INSERT 작업에 의해 레코드가 유령처럼 갑자기 보이게 되었다 ❗

다음과 같은 현상을 PHANTOM READ 라고 한다.

 

'SELECT…FOR_UPDATE' -> 해당 레코드를 조회함과 동시에 쓰기 잠금을 걸게 된다.

설명한대로 REPETABLE READ 단계에서 조회 시 변경이 있다면 언두 영역을 조회해야 한다.
하지만 언두 영역에 레코드는 쓰기 잠금이 불가능하다.
때문에 실제 데이터베이스의 값을 갖고올 수 밖에 없으며, 이전에 조회되지 않았던 추가된 데이터를 갖고오게 된다.

 

PHANTOM READ

다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다 안 보였다 하는 현상

 

 

 

4. SERIALIZABLE

가장 엄격한 격리 수준

모든 부정합 문제를 해결하지만, 읽기 작업 시에도 읽기 잠금을 획득해야 하기에 동시 처리가 불가능하다.

 

 

 

결론

몇일 날 잡아서 파고들어 글로 정리하니 이해함과 동시에 잊혀지지 않을 것 같다. 직접 쓰레드 2개 돌려서 코드로도 실험을 해보려 호기롭게 도전해보았지만, 아직 그정도 능력은 되지 않았다...

추후에 동시성 처리에 대해 더 공부하고 자바 쓰레드에 대해 깊게 알게된다면, 해당 글을 코드로 구현해보고 포스팅 해보아야겠다!