💡 본 포스팅은 CS 기술 면접 스터디에도 작성된 글 입니다.
트랜잭션 ACID
트랜잭션 ACID 에 관한 이론은 데이터베이스 트랜잭션(Transaction) 을 참고하자.
자바 기반의 스프링부트 웹 애플리케이션은 기본적으로 요청에 대해 트랜잭션 단위의 안전한 처리를 요구한다. 트랜잭션은 ACID 라는 성질을 지니고 있다. 원자성(Atomic), 일관성(Consistency), 고립성(Isolation), 지속성(Durability) 라는 4가지 속성이 만족되었을 때 해당 연산은 하나의 트랜잭션 단위라고 볼 수 있다.
트랜잭션 격리수준(Isolation level)
격리수준은 왜 등장했는가?
트랜잭션은 각 데이터베이스 시스템내에서 격리수준에 의해 동작한다. 또한 각 트랜잭션은 스프링 웹 애플리케이션과 같은 멀티 쓰레드 환경일 경우, 각 쓰레드에 의해 트랜잭션이 생성된다. 다시말해, 멀티 쓰레드환경에서 동시간대에 트랜잭션이 생성될 수 있으며, 생성된 여러 트랜잭션들이 동시간대에 공유 자원에 접근하여 동시성 이슈가 발생할 수 있다.
트랜잭션 격리수준은 이를 해결하기 위해 등장했다. 즉, 격리수준은 트랜잭션의 ACID 를 보장하면서도 각 트랜잭션간의 공유 자원 접근으로 인한 동시성 제어, 동시성 제어시 성능 저하를 최소화하기 위해 등장했다.
격리수준 4단계
격리수준은 크게 4가지로 분류된다.
- READ UNICOMMITED (커밋되지 않은 읽기)
- READ COMMITED (커밋된 읽기)
- REPREATABLE READ (반복 가능한 읽기)
- SERIALIZABLE (직렬화 기능)
READ UNCOMMITED 부터 시작하여 SERIALIZABLE 방향으로 내력갈 수록 더 엄격하고도 안전한 격리가 보장된다. 다시말해, ACID 성질 중 고립성(Isolation)
이 더욱 안전하게 보장된다. 하지만 격리수준이 높은 데이터베이스 시스템일수록 처리 성능이 떨어진다는 치명적인 단점이 존재하며, 마구잡이로 격리수준을 설정했을 때 데드락(Deadlock) 에 빠질 위험이 존재하니, 각 애플리케이션에 적절한 격리수준을 설정하는 것이 적합하다.
💡 데이터 정합성(안전한 고립성) 과 성능은 반비례한다.
격리수준에 따라 발생할 수 있는 문제점
트랜잭션 격리수준을 바로 알아보기전, 각 격리수준에서 발생할 수 있는 문제점이 존재한다. 이 문제점을 간단히 사전 지식으로 학습하고, 격리수준을 상세히 알아보도록 한다.
-
더티 리드 (Dirty Read)
: 더티 리드란 특정 트랜잭션에 의해 데이터가 변경되었지만, 아직 커밋되지 않은 상황에서 다른 트랜잭션이 해당 변경 사항을 읽어와버리는 문제를 뜻한다. -
반복 불가능한 조회 (Non-Repeatable Read)
: 같은 트랜잭션 내에서 같은 데이터를 여러번 조회했을 떄 읽어온 데이터가 다른 경우를 뜻한다. -
팬텀 리드 (Phantom Read)
: Non-Repeatable Read 의 한 종류로, 일겅온 결과가 새로운 행이 생겼거나 또는 없어진 현상을 뜻한다.
READ UNCOMMITED (커밋되지 않은 읽기)
가장 낮은 격리수준이다. 한 트랜잭션이 아직 커밋되지 않은 상태임에도 불구하고, 다른 트랜잭션에서 커밋도 안된 해당 변경사항을 읽어와버릴 수 있는 격리수준이다. 데이터 정합성이 깨질 가능성이 매우 높은 격리수준이지만, 성능은 가장 빠르다. 데이터를 일관성이 꺠져도 전혀 문제가 없는 연산에서 사용하기에 적합하다.
트랜잭션 A 와 B 가 동시에 수행되고 있다. 트랜잭션 A 에서 홍길동의 이름을 홍길동 2로 변경했으며, 아직 커밋 & 롤백을 하지 않았다.
이때 트랜잭션 B 가 아직 커밋되지 않은 A 의 변경 내용을 읽어와버리면 어떻게 될까? 불행하게도 이 격리수준에선 변경된 이름을 읽어와버릴 수 있다. 즉, 트랜잭션 B 는 트랜잭션 A 에서 아직 커밋되지 않은 내용을 조회할 수 있다.
발생할 수 있는 문제
- 더티 리드
- Non-Repeatable Read
- 팬텀 리드
READ COMMITED
한 트랜잭션의 변경내용중에 커밋이 완료된 데이터만 다른 트랜잭션에서 조회 가능한 격리수준이다. 즉, 트랜잭션이 수행되는동안 다른 트랜잭션에서 해당 데이터의 커밋되지 않고, 변경사항이 생기기 이전의 내용을 읽어온다. 가장 많이 사용되는 격리수준으로, Oracle DB 등에서 기본값으로 설정되어있다.
이전과 달리, 커밋되기 전에는 변경사항이 발생하기 이전의 값인 "홍길동" 이 그대로 조회되며, 커밋된 이후에는 "홍길동2" 가 조회되어 더티 리드
가 해결되었다. 하지만 문제점은, 트랜잭션 B 내에서 트랜잭션 A 의 커밋 이전/이후로 다른 데이터가 조회된다는 것이다.
발생할 수 있는 문제
특정 트랜잭션에서 데이터가 변경되었으나, 아직 커밋되지 않은 상태라면 다른 트랜잭션에선 해당 데이터에 접근했을 떄 트랜잭션 시작 전 데이터 값을 읽어온다. 커밋된 이후에서야 변경된 데이터를 읽어올 수 있다. 이 떄문에 팬텀 리드와 반복 불가능한 조회 문제가 발생한다.
- Non-Repeatable Read
- 팬텀 리드
REPEATABLE READ
트랜잭션이 시작되기 전에 커밋된 내용에 대해서만 조회할 수 있는 격리수준이다. 트랜잭션이 완료될 때 까지 SELECT 쿼리가 사용되는 모든 데이터에 공유 락(Shared Lock)
이 걸리는 수준이다. 트랜잭션 범위 내에서 조회한 내용이 항상 동일함을 보장하며, 다른 트랜잭션은 트랜잭션 영역에 해당되는 데이터에 대한 수정이 불가능하다.
특정 행을 조회시 항상 같은 데이터를 응답하는 것을 보장하는 격리수준이다. 하지만, SERIALIABLE 과 다르게 행이 추가되는 것을 막지는 않는다. 이 때문에 팬텀 리드가 발생할 수 있다.
발생할 수 있는 문제
- 팬텀 리드
SERIALIZABLE
한 트랜잭션에서 사용되고 있는 데이터는 다른 트랜잭션에서 절대 접근할 수 없는 격리수준이다. 즉, 특정 트랜잭션이 사용중인 테이블의 모든 행을 다른 트랜잭션이 접근할 수 없도록 잠근다. 이 격리수준에선 단순 SELECT 쿼리만 실행되더라도, DB Lock 이 걸려서 다른 트랜잭션에 의해 데이터에 접근할 수 없게된다.
발생할 수 있는 문제
- 다른 트랜잭션이 현재 트랜잭션에 해당되는 데이터에 대한 조회 및 수정 모두 불가능 (데드락 위험)