1. 트랜잭션 격리수준(Isolation Level)
트랜잭션은 각각의 세션에서 시작한 트랜잭션끼리 영향을 주지 않고, 영향을 받지 않는 독립성을 보장해야 합니다.
이러한 성질을 트랜잭션의 독립성(isolation) 이라고 하며, 그 독립을 제어하는 정도를 트랜잭션 격리수준 이라고 합니다.
일반적으로 사용되는 DBMS 들은 기본적으로 4단계의 격리수준을 지원하는데, 오늘은 이 격리수준에 대해 한 번 알아보겠습니다.
2. 4단계의 격리수준
MySQL 을 비롯한 많은 DBMS에서 아래와 같은 4가지의 격리수준을 지원합니다.
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
위에서 아래로 내려갈 수 록, 격리수준이 엄격해지며 그에 따른 DBMS 성능이 저하되게 됩니다.
각 격리수준이 어떤 방식으로 트랜잭션의 독립성을 처리하는지 살펴보겠습니다.
1) READ UNCOMMITTED
실제로 잘 사용하지 않는 격리수준입니다.
READ UNCOMMITTED 는 직역하는대로 "커밋되지 않은 것을 읽는다."는 의미인데요. A 세션이 데이터를 변경하고 Commit/Rollback 을 하지 않아도, B 세션에서 변경된 데이터를 조회할 수 있습니다.
즉, 변경한 데이터가 온전하게 데이터베이스에 반영되지 않는 불완전 데이터를 조회할 수 있게 되는데, 이러한 현상을 더티리드(Dirty Read) 라고 합니다.
세션A 가 insert한 사용자1의 정보는 아직 Commit 되지 않았기 때문에, 실제로 Users 테이블에 저장될 수 도 있고, 저장이 안될 수 도 있습니다. 이 상태에서 세션B 가 Users 테이블을 조회하게 되면, 앞서 세션 A 에서 insert 한 사용자1의 정보가 조회되는 것 입니다.
하지만, 세션A 에서 문제가 발생해 사용자1의 정보가 Rollback 된다면, Users테이블에는 사용자1의 정보가 저장되지 않는데, 세션B는 이미 Users 테이블을 조회해 사용자1의 정보를 취득했으니, 해당 데이터가 존재한다는 가정하에 다음 프로세스를 진행하게 됩니다.
이는 데이터 정합성에 치명적인 문제점을 발생시키기 때문에, 해당 격리수준은 잘 사용되지 않습니다.
2) READ COMMITTED
READ COMMITTED 격리수준은 Oracle DBMS에서 기본적으로 사용하는 격리수준입니다.
앞서 살펴본 UNCOMMITTED 에서 COMMITTED 로 변경된 격리수준으로, 특정 세션이 데이터를 변경해도 COMMIT 하지 않는다면 다른 세션에서는 해당 데이터를 조회할 수 없습니다.
즉, READ UNCOMMITTED 에서 발생했던 더티리드(Dirty Read)가 발생하지 않음을 의미합니다.
데이터가 변경되면 변경되기 전의 정보를 언두로그에 저장해놓고 실제 레코드를 변경시킵니다. 이후, 조회를 시도하는 경우 언두로그에 기록된 정보를 조회시켜줌으로써 더티리드를 방지합니다.
또한, 트랜잭션을 시작해서 데이터를 변경한 세션이 COMMIT을 하지 않는다면, 다른 세션에서는 해당 데이터를 조회할 수 없기 때문에 비교적 안정적으로 데이터 정합성을 유지할 수 있는 격리수준입니다.
실제로 웹 서비스를 운영하면서 해당 격리수준을 많이 사용하기도 하는데요. 이 격리수준도 한 가지의 데이터 부정합의 문제를 가지고 있습니다.
세션B가 한 트랜잭션 안에서 Users 테이블을 2번 조회하는 경우, 중간에 세션A가 Users 테이블의 데이터를 변경하고 Commit 을 하게 되면, 세션B는 같은 트랜잭션 안에서 같은 쿼리로 다른 결과를 가지게 됩니다. 이러한 데이터 부정합을 NON-REPEATABLE READ 라고 부릅니다.
3) REPEATABLE READ
REPEATABLE READ 격리수준은 MySQL(InnoDB엔진) 에서 기본으로 사용하는 격리수준입니다.
앞에서 살펴본 READ COMMITTED 에서 발생한 NON-REPEATABLE READ 가 발생하지 않는 격리수준으로, 변경되기 전의 데이터를 언두(Undo) 로그에 기록해뒀다가, 다른 트랜잭션이 조회를 시도하면 언두로그의 데이터를 조회시킴으로써 같은 트랜잭션에서 항상 같은 데이터를 조회하도록 보장합니다.
이 격리수준에선 Transaction ID(위 그림의 TX-ID) 라는 정보가 등장합니다.
세션B가 트랜잭션을 시작하는 시점은 TX-ID 가 10 이므로, 해당 트랜잭션에서 조회하는 데이터는 기본적으로 TX-ID 가 10보다 작은 레코드를 기준으로 조회하게 됩니다.
TX-ID 번호가 더 작다는 것은 더 이전에 실행된 트랜잭션의 결과를 의미하기 때문에, 세션A가 TX-ID 13을 부여받고 데이터를 변경했다 해도, 세션B의 TX-ID 가 12로 더 이전에 시작했으므로 세션A의 변경 결과는 조회하지 않는 것 입니다.
하지만, REPEATABLE READ 격리수준 또한 정합성 문제가 발생할 수 있습니다.
세션 B 에서 트랜잭션을 시작하고, 레코드의 락을 획득하기 위해 select * from users ~ for update 구문을 사용한 케이스 입니다.
이 경우, 언두 레코드(회색으로 칠해진 TX-ID 13 정보)에는 락을 걸 수 없기 때문에 현재 레코드를 기준으로 락을 획득하여 결과를 조회하게되고, 그 결과로 같은 트랜잭션에서 다른 결과가 조회될 수 있게 됩니다.
이렇게, 다른 트랜잭션의 작업에 따라 레코드가 보였다가 안보였다가 하는 현상을 팬텀리드(PHANTOM READ) 라고 부릅니다.
4) SERIALIZABLE
가장 강력한 격리수준입니다. 한 트랜잭션이 시작돼서 Users 테이블을 조회하기 시작하면, 해당 트랜잭션이 끝날 때 까지 다른 트랜잭션에서는 Users 테이블에 접근할 수 없도록 하는 격리수준입니다. 조회만으로도 데이터의 잠금을 걸기 때문에 동시성이 현저하게 떨어지고, 이는 데이터베이스의 성능이 낮아짐을 의미합니다.
이런 경우, 앞서 만나본 팬텀리드(Phantom Read)는 발생하지 않지만, MySQL 의 InnoDB 엔진의 경우 REPEATABLE READ 에서도 팬텀리드가 발생하지 않기 때문에, SERIALIZABLE 격리수준을 굳이 사용하지 않습니다.
'DB&JPA' 카테고리의 다른 글
[MySQL] 계정 생성 및 권한 부여, 그리고 역할 (0) | 2024.02.06 |
---|---|
[JPA] JPA 쿼리 로그 및 파라미터 바인딩 확인하기 (SpringBoot3 버전) (0) | 2023.07.08 |
[JPA] @AttributeOverrides 와 @Embedded 로 간결한 도메인 객체 만들기 (0) | 2023.07.06 |
[QueryDSL] SpringBoot3 버전 QueryDSL 설정하기 (0) | 2023.06.30 |
댓글