Isolation Level(격리 수준)
격리 수준(Isolation Level)은 여러 트랜잭션이 동시에 실행될 때, 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터에 접근하는 것을 어느 수준까지 허용할 것인지를 결정하는 설정이다.
트랜잭션 격리 수준의 필요성
격리 수준이 낮을수록 동시성은 높아지지만, 다양한 데이터 부정합(Concurrency Issue) 문제가 발생할 가능성이 커진다.
더티 리드 (Dirty Read): 한 트랜잭션이 아직 커밋(Commit)되지 않은 다른 트랜잭션의 변경 데이터를 읽는 현상
데이터를 변경한 트랜잭션이 롤백(Rollback)되면, 데이터를 읽은 트랜잭션은 존재하지 않는 데이터를 참조하게 되어 데이터 불일치 문제가 발생
반복 불가능한 읽기 (Non-Repeatable Read): 한 트랜잭션 내에서 동일한 조건으로 두 번 이상 SELECT 쿼리를 실행했을 때 각 조회 결과가 다르게 나타나는 현상
그 사이에 다른 트랜잭션이 데이터를 수정하고 커밋하여 한 번 조회했던 레코드의 값이 중간에 변경되는 문제
팬텀 리드 (Phantom Read): 한 트랜잭션 내에서 동일한 조건으로 첫 번째 조회에서는 없었던 새로운 레코드가 두 번째 조회에서 나타나는 현상
다른 트랜잭션이 새로운 레코드를 추가하고 커밋하여 발생하는 문제
격리 수준의 종류
SQL 표준에서 정의하는 격리 수준은 네 가지이며, InnoDB 스토리지 엔진은 이 네 가지를 모두 지원한다.
READ UNCOMMITTED
O
O
O
READ COMMITTED
X
O
O
REPEATABLE READ
X
X
O(InnoDB X)
SERIALIZABLE
X
X
X
아래로 갈수록 격리 수준이 높아져 데이터 정합성은 강화되지만, 잠금의 범위가 넓어지고 길어져 동시 처리 성능은 저하된다.
READ UNCOMMITTED
가장 낮은 격리 수준으로, 한 트랜잭션의 변경 내용이 커밋이나 롤백 여부와 관계없이 다른 트랜잭션에 즉시 노출된다.
BEGIN
INSERT OGU
SELECT OGU
정상 조회
COMMIT
READ COMMITTED
오라클 DBMS에서 기본으로 사용되는 격리 수준으로, 온라인 서비스에서 가장 많이 사용되는 격리 수준이다.
DIRTY READ
가 발생하지 않으며, 데이터를 변경했을 경우COMMIT
이 완료된 데이터만 다른 트랜잭션에서 조회 가능조회하려는 데이터가
COMMIT
이 완료되지 않았다면, 언두 영역에서 백업된 레코드 조회
BEGIN
no: 59 name: DDUZY
UPDATE SET name = 'OGU' WHERE no = 59
SELECT WHERE no = 59
변경 전 데이터를 언두 로그로 복사
DDUZY로 조회(언두 로그의 이전 데이터를 조회)
COMMIT
no: 59 name: OGU
이 격리 수준에서도 한 트랜잭션에서 동일한 데이터를 여러 번 조회하면, 각각의 조회는 동일한 결과를 반환하지 않을 수 있다.(NON-REPEATABLE READ
발생)
BEGIN
no: 59 name: OGU
SELECT WHERE no = 59
OGU로 조회
BEGIN
UPDATE SET name = 'DDUZY' WHERE no = 59
COMMIT
no: 59 name: DDUZY
SELECT WHERE no = 59
(같은 쿼리 실행)
DDUZY로 조회
일반적인 웹 애플리케이션에서는 문제가 없을 수 있으나, 하나의 트랜잭션에서 동일한 데이터를 여러 번 조회하고 변경하는 금전적인 거래와 같은 경우에는 문제가 발생할 수 있다.
REPEATABLE READ
InnoDB 스토리지 엔진에서 기본으로 사용되는 격리 수준으로, MVCC(Multi-Version Concurrency Control)를 이용한 방식이다.
트랜잭션이 시작되는 시점에 스냅샷을 생성하여, 이후 트랜잭션 내의 모든 SELECT 쿼리는 해당 스냅샷을 기준으로 데이터를 조회(=MVCC)
다른 트랜잭션이 데이터를 변경하고 커밋하는 경우, 현재 트랜잭션은 자신의 트랜잭션 번호보다 낮은 트랜잭션 번호를 가진 언두 로그의 데이터를 조회
MVCC(Multi Version Concurrency Control)를 이용한 Non-Repeatable Read
방지
Non-Repeatable Read
방지모든 InnoDB 트랜잭션에는 고유한 트랜잭션 번호(순차적 증가 값)가 부여되며, 언두 영역에 백업된 레코드에는 트랜잭션 번호가 저장된다.
TRX-ID: 6 no: 59 name: DDUZY
BEGIN(TRX-ID: 10)
SELECT WHERE no = 59
DDUZY로 조회
BEGIN(TRX-ID: 12)
UPDATE SET name = 'OGU' WHERE no = 59
변경 전 데이터를 언두 로그로 복사
COMMIT(TRX-ID: 12)
TRX-ID: 12 no: 59 name: OGU TRX-ID: 6 no: 59 name: DDUZY
SELECT WHERE no = 59
자신의 트랜잭션 번호보다 작은 번호 중 최근 것 조회
DDUZY로 조회
트랜잭션 번호를 이용해 트랜잭션 12에서 데이터가 변경되었지만, 트랜잭션 10은 자신의 번호보다 작은 트랜잭션의 번호를 조회하여 언두 로그에 있는 데이터를 조회하게 된다.
넥스트 키 락을 이용한 PHANTOM READ
방지
PHANTOM READ
방지위와 같이 잠금 없이 조회하는 경우엔 MVCC를 통해 데이터 무결성을 보장하지만, 베타적 잠금을 걸게 되면 PHANTOM READ
가 발생할 수 있다.
일반적인 DBMS 기준
일반적인 DBMS에서 베타적 잠금을 걸어 조회하게 되면 PHANTOM READ
가 발생하여 동일한 쿼리를 실행했을 때 결과가 달라질 수 있다.
TRX-ID: 6 no: 59 name: DDUZY
BEGIN(TRX-10)
59인 레코드만 잠금
SELECT WHERE no >= 59 FOR UPDATE
DDUZY 조회
BEGIN(TRX-ID: 12)
INSERT INTO t1 VALUES (60, 'OGU')
(대기 없이 바로 실행)
COMMIT(TRX-ID: 12)
TRX-ID: 6 no: 59 name: DDUZY TRX-ID: 12 no: 60: name: OGU
SELECT WHERE no >= 59 FOR UPDATE
DDUZY, OGU 조회
id = 59인 레코드에 대해서만 잠금이 걸리고, 트랜잭션 12의 요청은 잠금 없이 즉시 실행이 되고, 이후 조회에서 없던 id = 60인 레코드가 조회되어 PHANTOM READ
가 발생하게 된다.
InnoDB 스토리지 MySQL 기준
하지만 MySQL에서는 넥스트 키 락이 존재하기 때문에 트랜잭션 12의 요청이 잠금을 획득하기 위해 대기하게 되고, 결국에는 트랜잭션 10은 DDUZY만 조회하게 된다.
TRX-ID: 6 no: 59 name: DDUZY
BEGIN(TRX-10)
59뿐만 아니라 59 이상의 레코드에 대해서도 잠금
SELECT WHERE no >= 59 FOR UPDATE
DDUZY 조회
BEGIN(TRX-ID: 12)
INSERT INTO t1 VALUES (60, 'OGU')
넥스트 키 락 잠금 대기
...
SELECT WHERE no >= 59
...
DDUZY 조회
...
COMMIT(TRX-10)
COMMIT(TRX-ID: 12)
TRX-ID: 6 no: 59 name: DDUZY TRX-ID: 12 no: 60: name: OGU
갭락 존재로 일반적으로 MySQL에서는 Phantom Read
가 발생하지 않지만, 발생 할 수 있는 시나리오는 다음과 같다.
첫 번째 조회는 잠금 없이 조회
두 번째 조회에서 베타적 잠금을 획득하여 조회
하지만 이렇게 조회하는 경우는 일반적으로 없기 때문에 Phantom Read
가 거의 발생하지 않는다고 볼 수 있다.
SERIALIZABLE
가장 단순하고 엄격한 격리 수준으로, 그만큼 동시 처리 성능이 다른 격리 수준보다 떨어진다.
읽기 작업도 공유 잠금(읽기 잠금)을 획득을 설정
잠금이 걸린 경우 다른 트랜잭션에서 해당 레코드를 변경하거나 삭제할 수 없음
하지만 InnoDB 스토리지 엔진에서는 READ COMMITTED
격리 수준에서도 PHANTOM READ
문제를 해결할 수 있어 해당 격리 수준을 사용하지 않는다.
참고자료
Last updated
Was this helpful?