트랜잭션 격리수준(Isolation Level)이란, 여러 트랜잭션이 동시에 처리될 때 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있게 허용할지 말지를 결정하는 것입니다.
트랜잭션 격리 수준은 크게 READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE로 나뉩니다.
4개의 격리 수준에서 뒤로 갈수록 각 트랜잭션 간의 데이터 격리(고립) 정도가 높아지며, 동시 처리 성능도 떨어지는 것이 일반적입니다.
각 트랜잭션에서의 변경 내용이 COMMIT 되거나 ROLLBACK 여부에 상관없이 다른 트랜잭션에서 작업 내용이 보입니다.
특정 트랜잭션에서 처리하는 작업이 완료되지 않았는데도 다른 트랜잭션에서 볼 수 있는 현상을 더티 리드(Dirty Read)라고 합니다.
해당 격리 수준은 정합성에 문제가 많기에 사용을 권장하지 않는 격리 수준입니다.
해당 격리수준은 Oracle DBMS에서 기본적으로 사용하는 격리 수준입니다.
해당 격리 수준은 사용자A 트랜잭션에서 데이터를 변경했더라도 COMMIT이 완료된 데이터만 다른 트랜잭션에서 조회할 수 있습니다.
트랜잭션A에서 데이터를 변경을 시도하면 즉시 해당 테이블의 데이터가 변경됩니다. 하지만, 다른 트랜잭션이 변경된 데이터를 접근하려고 한다면 Undo 로그에 기록된 백업 데이터를 조회하거게 되기에 Commit이 되기 이전에는 다른 트랜잭션에서 새로운 데이터를 참조할 수 없습니다.
READ COMMITTED 격리 수준에서도 NON-REPEATABLE READ(REPEATABLE READ 불가능)라는 부정합의 문제가 존재합니다.
사용자 B가 BEGIN 명령 후 트랜잭션을 시작하고 "JAEMIN"을 조회했을 때 존재하지 않았던 데이터가
사용자 A에서 "JAEMIN" 데이터를 생성 후 COMMIT을 실행 하였고, 다시 사용자 B가 "JAEMIN"을 조회한다면 결과를 반환받습니다.
위 예시에서 별 다른 문제가 없어 보여도 사용자 B가 하나의 트랜잭션 내에서 똑같은 SELECT 쿼리를 실행했을 때 항상 같은 결과를 가져와야 한다는 REPEATABLE READ 정합성에 어긋나는 것입니다.
금전적인 업무를 수행하는 과정에서는 크리티컬한 문제가 발생할 수 있습니다. 합계를 계산하기 위해 동일 데이터를 여러 번 읽고 변경하는 과정에서 다른 트랜잭션에서는 입,출금이 반복될텐데 조회해온 데이터가 다를때는 데이터 정합성이 깨지게됩니다.
해당 격리수준은 MySQL의 InnoDB 스토리지 엔진에서 기본적으로 사용되는 격리 수준입니다.
InnoDB 스토리지 엔진은 트랜잭션이 Rollback될 가능성에 대비해 변경되기 전 레코드를 언두(Undo) 공간에 백업해두고 실제 레코드 값을 변경합니다. 즉, MVCC 방식을 이용합니다.
READ COMMITTED에서 발생했던 NON-REPEATABLE READ는 발생하지 않지만, 해당 격리 수준에서는 PHANTOM READ 현상이 보일 수 있습니다.
PHANTOM READ는 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드가 보였다 안 보였다 하는 현상을 말합니다.
갭 락(Gap Lock)을 사용하지 않는 경우
InnoDB의 MVCC는 기본적으로 트랜잭션 간의 레코드 추가, 삭제를 관리하지만,
갭 락이 적용되지 않는 상황에서는 새로운 레코드가 삽입 될 수 있습니다.
이로인해 팬텀 리드가 발생할 수 있습니다.
1. T1 트랜잭션이 SELECT * FROM employees WHERE salary > 50000; 쿼리로 salary가 50000 이상인 레코드를 조회합니다.
2. T2 트랜잭션이 새로운 salary가 60000인 레코드를 삽입하고 커밋합니다.
3. T1이 다시 동일한 쿼리를 실행하면, 앞서 조회했던 데이터에는 없던 salary가 60000인 레코드가 추가되어 있습니다.
사용자 A가 테이블에 INSERT를 실행하는 도중에 사용자 B가 SELECT ... FOR UPDATE 쿼리로 테이블을 조회핸다면
REPEATABLE READ 격리 수준에서는 동일한 트랜잭션에서 수행한 SELECT의 결과는 동일해야 합니다.
하지만 SELECT ... FOR UPDATE 결과 쿼리는 그렇지 않습니다. 이로 인해 PHANTOM READ 부정합이 발생합니다.
(SELECT ... FOR UPDATE는 Undo를 조회하는 것이 아닌 레코드 값을 조회합니다.)
즉, REPEATABLE READ 격리 수준에서는 기본적으로 MVCC를 통해 트랜잭션 시작된 시점의 일관된 데이터를 유지하지기에 팬텀 리드가 발생하지 않도록 방지합니다.
하지만, 갭 락이 적용되지 않는 상황이나 범위 기반 쿼리에서 명시적 잠금(SELECT FOR UPDATE)이 없을 때, 특정한 범위 내에 새로운 레코드가 추가되면 팬텀 리드가 발생할 수 있습니다.
SERIALIZABLE은 가장 단순한 격리 수준이면서 동시 처리 성능도 떨어지는 격리 수준입니다.
해당 격리 수준을 사용한다면 Non-locking consistent read(잠금이 필요 없는 일관된 읽기) 를 사용하지 않고 읽기 작업에도 공유 잠금을 획득해야만 하며, 동시에 다른 트랜잭션은 해당 레코드를 변경하지 못하게 됩니다. 즉, 한 트랜잭션에서 읽고 쓰는 레코드를 다른 트랜잭션에서는 절대 접근할 수 없는 것입니다.
해당 격리 수준에서는 일반적인 DBMS에서 발생하는 PHANTOM READ 문제가 발생하지 않지만, InnoDB 스토리지 엔진에서는 갭 락과 넥스트 키 락 덕분에 REPEATABLE READ 격리 수준에서도 이미 PHANTOM READ가 발생하지 않기에 굳이 SERIALIZABLE을 사용할 필요는 없어 보입니다.