ACID 특성 가지고 있다.
트랜잭션은 원자성, 일관성, 지속성을 보장한다. 문제는 격리성이다.
트랜잭션간에 격리성을 완벽히 보장하려면 트랜잭션을 거의 차례대로 실행해야한다. 이렇게 하면 동시성 처리 성능이 매우 나빠진다.
이런 이유로 ANSI 표준은 트랜잭션의 격리 수준을 4단계로 나누어 정의했다.
순서대로 READ UNCOMMITED의 격리수준이 가장 낮고, SERIALIZABLE의 격리수준이 가장 높다
격리수준이 낮을 수록 동시성은 증가하지만, 격리수준에 따른 다양한 문제가 발생한다.
발생하는 문제의 종류는 다음 3가지가 있다.
격리수준이 낮을수록 더 많은 문제가 발생한다.
격리수준 | DIRTY READ | NON-REPEATABLE READ | PHANTOM READ |
---|---|---|---|
READ UNCOMMITED | O | O | O |
READ COMMITED | O | O | |
REPEATABLE READ | O | ||
SERIALIZABLE |
커밋하지 않은 데이터를 읽을 수 있다. 예를 들어서 트랜잭션1이 데이터를 수정하고 있는데 이를 커밋하지 않아도 트랜잭션2가 수정중인 데이터를 조회할 수 있다. 이것을 DIRTY READ라고 한다.
트랜잭션2가 DIRTY READ를 사용하는데 트랜잭션1이 롤백하면, 데이터의 정합성에 심각한 문제가 발생할 수 있다.
DIRTY READ를 허용하는 격리수준을 READ UNCOMMITED라고 한다.
커밋한 데이터만 읽을 수 있다. 따라서 DIRTY READ가 발생하지 않는다.
하지만 NON-REPEATABLE READ가 발생할 수 있다.
예를들어 트랜잭션 1이 회원A를 조회중인데, 갑자기 트랜잭션2가 회원A를 수정하고 커밋하면 트랜잭션1이 다시 회원A를 조회했을 때, 수정된 데이터가 조회된다. 이처럼 반복해서 같은 데이터를 읽을 수 없는 상태를 NON-REPEATABLE READ라고 한다.
DIRTY READ는 허용하지 않지만, NON-REPEATABLE READ는 허용하는 격리수준을 READ COMMITED라고 한다.
한 번 조회한 데이터를 반복해서 조회해도 같은 데이터가 조회된다. 하지만 PHANTOM READ는 발생할 수 있다.
예를들어 트랜잭션1이 10살이하의 회원을 조회했는데 트랜잭션2가 5살 회원을 하나 추가하고 커밋하면 트랜잭션1이 다시 10살 이하의 회원을 조회했을 때 회원하나가 추가된 상태로 조회된다.
이처럼 반복 조회시 결과집합이 달라지는 것을 PHANTOM READ라고 한다.
NON-REPEATABLE READ는 허용하지 않지만, PHANTOM READ는 허용하는 격리수준을 REPEATABLE READ라고 한다.
가장 엄격한 트랜잭션 격리 수준이다. 여기서는 PHANTOM READ가 발생하지 않지만 동시성 처리 성능이 급격히 떨어질 수 있다.
애플리케이션은 보통 동시성 처리가 중요하므로 데이터베이스들은 보통 READ COMMITTED 격리수준을 기본으로 사용한다. 일부 중요한 비즈니스 로직에 더 높은 격리수준이 필요하면 데이터베이스 트랜잭션이 제공하는 잠금기능을 사용하면 된다.
참고링크 :
https://feco.tistory.com/45
https://private-space.tistory.com/97 (설명 그림 good)
https://nesoy.github.io/articles/2019-05/Database-Transaction-isolation (위의 링크 먼저 본 후 보기)
데이터베이스 연산(read/write)을 수행하기 전에 해당 데이터에 먼저 lock 연산을 실행하여 독점권을 획득하는 방식으로 트랜잭션의 직렬가능성을 보장하는 방식
병행 수행되는 트랜잭션들이 동일한 데이터에 동시에 접근하지 못하도록 lock과 unlock이라는 2개의 연산을 이용해 제어.
기본 원리는 먼저 접근한 데이터에 대한 연산을 다 마칠 때까지, 해당 데이터에 다른 트랜잭션이 접근하지 못하도록 상호배제하여 직렬가능성을 보장하는 것.
데이터를 읽을 때 사용되는 lock
공유락 끼리는 동시에 접근 가능. 즉, 하나의 데이터를 읽는 것은 여러 사용자가 (트랜잭션이) 동시에 할 수 있다.
하지만 공유락이 설정된 데이터에 배타락을 사용할 수는 없다.
배타락은 데이터를 변경하고자 할 때 사용됨.
트랜잭션이 완료될 때까지 유지된다.
배타락은 lock이 해제될 때까지 다른 트랜잭션(읽기 포함)은 해당 리소스에 접근할 수 없다. 또한 해당 lock은 다른 트랜잭션이 수행되고 있는 데이터에 대해서는 접근하여 함께 lock을 설정할 수 없다.
로킹 단위가 클수록 제어는 쉽지만 병렬성이 떨어지고
로킹 단위가 작을 수록 제어는 어렵지만 병렬성은 높아진다.
블로킹은 lock간에 (배타-배타, 배타-공유)의 경합이 발생해서 특정 트랜잭션이 작업을 진행하지 못하고 멈춰선 상태를 말한다.
공유락끼리는 블로킹이 발생하지 않지만, 배타락은 블로킹을 발생시킨다.
블로킹을 해소하기 위해선, 이전의 트랜잭션이 완료 (커밋 or 롤백)되어야 한다.
뒤에 들어온 트랜잭션은 이전 트랜잭션이 마무리되어야 이후 진행이 가능.
이런 경합은 성능에 좋지않은 영향을 미치기 때문에 경합을 최소화할 필요가있다.
위 그림에서 트랜잭션 A가 로우를 업데이트하므로 2번 로우에 배타락을 걸었다. 트랜잭션 B는 2번 데이터를 읽어와야하는데, 해당 로우에 배타락이 걸려있으므로, 해당 락을 건 트랜잭션이 락을해제하고 종료되는 것을 기다린다. 이것이 바로 블로킹이다.
두 트랜잭션이 각각 락을 설정한 다음 서로의 락에 접근하여 값을 얻어오려고 할 때, 이미 각각의 트랜잭션에 의해 락이 설정되어있기 때문에 양쪽 트랜잭션 모두 영원히 처리되지 않게 되는 상태를 말함.
위와 같은 그림이 바로 교착상태를 나타낸다.
트랜잭션 A는 game-master의 2번 로우를 업데이트 한 후 game-detail 테이블의 2번 로우를 수정한다. 트랜잭션 B는 반대로 game-detail의 2번 로우를 수정한 후, game-master의 2번 로우를 수정한다.
각각의 트랜잭션들이 하나의 로우에 배타적 락을 가지고 있고, 상대가 가진 락이 해제되기를 기다리고 있으므로 이 상태가 영원히 지속되게 되는데 이를 교착상태라한다.
따라서 교착상태가 발생하면 DBMS가 둘 중 한 트랜잭션에 에러를 발생시킴으로써 문제를 해결한다. 교착상태가 발생할 가능성을 줄이기 위해서는 접근 순서를 동일하게 하는 것이 중요! 위의 예시에서는 game-master를 먼저 접근한 후, game-detail을 접근한다 같은 규칙을 정해 테이블 접근의 교차가 일어나지 않도록 하는 것이 중요하다.