트랜잭션 격리

공덕이형·2024년 1월 2일
0

CS

목록 보기
13/17

트랜잭션 격리

저번 포스팅에서 트랜잭션 락에 대해서 알아봤다.
트랜잭션 락의 경우는 두 트랜잭션이 (쓰기, 쓰기)의 상황을 해결하기 위함이다.

만약, (읽기, 쓰기)의 상황에서 락을 사용하게 된다면 두 트랜잭션의 동시 진행 정도를 과도하게 막게 된다. 동시성을 높이기 위해 좀 더 완화된 방법을 사용해야만 한다.

(읽기, 쓰기) 상태에선 갱신손실 같이 큰 문제가 발생하지 않는다. 다만, 오손 읽기(Dirty read), 반복불가능 읽기(non-repeatable read)문제, 유령 읽기(phantom read) 문제가 발생할 수 있다.

오손 읽기(Dirty Read)

오손 읽기는 읽기 작업을 하는 트랜잭션 1(T1)이 쓰기 작업을 하는 트랜잭션 2(T2)가 작업 중에 데이터를 읽기 때문에 발생하는 문제다. T2가 어떠한 원인으로 인해 정상적으로 종료되지 않고 롤백되었지만, 중간에 T1이 변경된 데이터를 불러왔을 때 T2는 롤백이 되었지만 T1은 정상적이지 않은 데이터를 읽는 것을 말한다.

반복 불가능 읽기(Non-repeatable read)

T1이 데이터를 읽고 T2가 데이터를 쓴 후(Update), T1이 다시한번 데이터를 읽을 때 생기는 문제다. 즉 T1의 결과가 처음과 마지막이 달라지는 현상을 반복 불가능 읽기 현상이라 한다.

이는 오손읽기와 달리 T2가 정상적으로 Commit 되었기에 틀린 데이터는 아니다.
다만, T1입장에선 같은 SQL문이지만 다른 결과가 도출된 문제가 발생한다.

유령 읽기(Phontom read)

T1이 데이터를 읽고 T2가 데이터를 쓰고(Insert) T1이 다시 한 번 데이터를 읽을 때 이전에 없던 데이터가 나타나는 현상.

반복 불가능 읽기와 비슷한 현상으로 틀린 데이터를 읽은것은 아니다.
그러나, 없던 데이터가 삽입되기 때문에 다르게 구분하게 된다.

트랜잭션 격리레벨

고립수준 \ 문제오손 읽기반복 불가능 읽기유령데이터 읽기
READ UNCOMMITTEDOOO
READ COMMITTEDXOO
REPETABLE READXXO
SERIALIZABLEXXX

SQL 표준에서는 고립수준을 위의 표처럼 4가지로 정의한다.

READ UNCOMMITTED

표에서 보듯 모든 데이터 부정합 문제가 발생할 수 있는 격리레벨이다.
커밋하지 않은 데이터 조차도 접근할 수 있다. 즉 UNCOMMITTED에서는 다른 트랜잭션의 작업이 커밋 또는 롤백되지 않아도 즉시 보이게 된다. 자신의 데이터에 아무런 공유락을 걸지 않는다 (배타락은 갱신손실 문제가 있기 때문에 걸어야만 한다)

이 격리레벨은 사용하지 않아야 한다.

READ COMMITTED

커밋된 데이터만 조회할 수 있는 격리수준이다. 반복읽기를 수행하면 다른 트랜잭션의 커밋 여부에 따라 조회 결과가 달라지게 된다. 반복 읽기 불가능 문제가 발생한다. 그러나, Non-Repetable Read는 일반적인 경우에는 크게 문제되지 않는다. 허나, 금전적인 처리와 같이 데이터 부정합이 발생하면 안되는 경우와 연결되면 문제가 될 수 있다.

오라클의 경우 READ COMMITTED가 기본설정이다

REPETABLE READ

일반적인 DBMS는 변경 전의 레코드를 UNDO 공간에 백업하게 된다. 그러면 변경 전/후 데이터가 모두 존재하므로 동일한 레코드에 대해 여러 버전이 존재하게 되는데 이를 MVCC(Multi - Version - Concurrency - Control) 이라 한다. MVCC를 통해 트랜잭션이 롤백된 경우에 데이터를 복원할 수 있고, 서로 다른 트랜잭션 간에 접근할 수 잇는 데이터를 세밀하게 제어할 수 있다.

그러나 REPETABLE READ에는 유령읽기가 발생할 수 있다. 즉 새로운 레코드의 추가 까지는 막지 않는것인데 MVCC덕분에 유령읽기는 일반적인 조회에서 발생하지 않게 된다. SELECT보다 나중에 실행된 트랜잭션이 INSERT한 레코드는 무시하면 되기 때문

MYSQL은 REPETABLE READ가 디폴트값임

SERIALIZABLE

SERIALIZABLE은 가장 엄격한 격리 수준으로, 이름 그대로 트랜잭션을 순차적으로 진행시킨다. SERIALIZABLE에서 여러 트랜잭션이 동일한 레코드에 동시 접근할 수 없으므로, 어떠한 데이터 부정합 문제도 발생하지 않는다. 그러나 트랜잭션이 순차적으로 처리되어야 하므로 동시 처리 성능이 매우 떨어진다.

MySQL에서 SELECT FOR SHARE/UPDATE는 대상 레코드에 각각 읽기/쓰기 잠금을 거는 것이다. 하지만 순수한 SELECT 작업은 아무런 레코드 잠금 없이 실행되는데, 잠금 없는 일관된 읽기(Non-locking consistent read)란 순수한 SELECT문을 통한 잠금 없는 읽기를 의미하는 것이다.

하지만 SERIALIZABLE 격리 수준에서는 순수한 SELECT 작업에서도 대상 레코드에 넥스트 키 락을 읽기 잠금(공유락, Shared Lock)으로 건다. 따라서 한 트랜잭션에서 넥스트 키 락이 걸린 레코드를 다른 트랜잭션에서는 절대 추가/수정/삭제할 수 없다. SERIALIZABLE은 가장 안전하지만 가장 성능이 떨어지므로, 극단적으로 안전한 작업이 필요한 경우가 아니라면 굳이 사용하지 않는다.

정리 및 요약

격리 수준이 높아질 수록 MySQL 서버의 처리 성능이 많이 떨어질 것으로 생각할지 몰라도, 사실 SERIALIZABLE이 아니면 크게 성능개선 및 저하는 발생하지 않는다. 그 이유는 결국 언두 로그를 통해 레코드를 참조하는 과정이 거의 동일하기 때문이다. 따라서 MySQL은 갭 락을 통해 Phantom Read까지 거의 발생하지 않고, READ COMMITTED보다는 동시 처리 성능은 뛰어난 REPEATABLE READ를 사용한다.

profile
형이 먹여살릴게

0개의 댓글