트랜잭션의 격리 수준(Isolation Level)이란 여러 트랜잭션이 동시에 처리될 때 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼 수 있게 허용할지 말지를 결정하는 것이다.
격리 수준은 크게 READ UNCOMMITTED
, READ COMMITTED
, REPEATABLE READ
, SERIALIZABLE
의 4가지로 나뉜다.
READ UNCOMMITTED 격리에서는 각각 트랜잭션에서 변경 내용이 COMMIT 이나 ROLLBACK 여부에 상관없이 다른 트랜잭션에서 조회할수 있다.
예시는 아래와 같다.
START TRANSACTION;
INSERT INTO users VALUES(1, 'Hong');
SELECT * FROM users WHERE id = 1 AND first_name = 'Hong';
COMMIT;
이처럼 하나의 트랜잭션에서 COMMIT 되지도 않았는데 다른 트랜잭션에서 조회할 수 있는 문제를 더티리드(Dirty Read) 라고 한다.
READ COMMITTED는 오라클에서 기본으로 사용되는 격리 수준이며, 위에서 발생한 더티리드(Dirty Read)는 발생하지 않는다. COMMIT 완료된 데이터만 조회할 수있기 때문이다.
예시를 한번 보자.
START TRANSACTION;
SELECT * FROM users WHER first_name = 'Hong';
START TRANSACTION;
UPDATE users SET first_name = 'Hwang';
COMMIT;
그 이유는 커밋되기 전까지는 변경된 내역을 조회할 수 없기 때문이다.
이 격리수준에서는 NON-REPEATABLE READ
이라는 부정합의 문제가 발생을 한다. 이 문제는 하나의 트랜잭션내에서 똑같은 SELECT 쿼리를 실행 했을때 항상같은 결과를 조회해야되는데, 조회할 때마다 결과가 달라진다.
REPEATABLE READ는 MySQL의 InnoDB 에서 기본적으로 사용한다.
이 격리 수준에서는 동일한 트랜잭션 내에서는 동일한 쿼리 결과를 보장한다.
한 트랜잭션에서 데이터를 읽어오면 해당 데이터는 현재 트랜잭션이 끝날 때까지 다른 트랜잭션에 의해 수정되지 않는다. 따라서 위에서 발생한 NON-REPEATABLE READ
부정합이 발생하진 않는다.
그 이유는 트랜잭션이 RollBack 이 될 가능성에 대비해 변경되기 전 레코드를 언두 영역에 저장해두기 때문에 일관성 있는 데이터를 유지할 수있다.
언두영역에 저장된 모든 레코드에는 트랜잭션 번호가 있다. 레코드들은 스토리지 엔진으로 부터 필요없다고 생각되면 삭제를 하게 된다.
자... 해당 격리 수준이 작동하는 방식은 다음과 같다.
START TRANSACTION;
SELECT * FROM users WHERE = 1;
START TRANSACTION;
UPDATE users SET first_name = 'Toto' WHERE id = 1;
COMMIT;
SELECT * FROM users WHERE id = 1;
그리고 REPEATABLE READ 에서도 부정합이 발생할 수 있는데 그것을 팬텀리드(Phantom Read) 라고 한다. 아래의 예시에서 확인해보자.
START TRANSACTION;
SELECT * FROM users WHERE id >= 1 FOR UPDATE;
START TRANSACTION;
INSERT INTO users VALUES (2, 'Hwang');
COMMIT;
SELECT * FROM WHERE id >= 1 FOR UPDATE;
위의 3번의 결과는 1번의 결과와 같아야하지만 다르다. 이 현상을 위에서 말한 팬텀리드(Phantom Read)라고 한다.
이 현상은 갭 락과 넥스트 키락으로 REPEATABLE READ 격리 수준에서는 발생하지않으니 격리성을 한단계 더 높일 필요는 없다.
SERIALIZABLE이 설정되면 트랜잭션에서 읽기작업시 공유잠금을 무조건 획득해야 되며 다른 트랜잭션은 해당 레코드에 접근할 수 없게된다.
보통 SERIALIZABLE 설정까지 하진 않는다.
참고