트랜잭션의 격리 수준이란 여러 트랜잭션이 동시에 처리 될 때, 다른 트랜잭션에 의해 영향을 받는 정도이다.
트랜잭션 격리 수준은 아래와 같이 4단계로 이루어져있다.
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
각 단계에서 발생할 수 있는 상황과 격리수준 및 동시 처리 성능은 아래와 같다.
DIRTY READ | NON-REPEATABLE READ | PHANTOM READ | 격리 수준 | 동시 처리 성능 | |
READ UNCOMMITTED | O | O | O | 낮음 | 높음 |
READ COMMITTED | X | O | O | ||
REPEATABLE READ | X | X | X | ||
SERIALIZABLE | X | X | X | 높음 | 낮음 |
일반적으로 격리 수준이 높아질수록 동시 처리 성능이 크게 떨어진다고 하지만, mysql에서 serializable을 제외한 나머지 수준에서는 큰 성능 차이는 없다.
이전에 Mysql 아키텍쳐를 설명하며 언두로그에 대해 설명한적이 있다.
변경사항이 발생한 데이터의 경우 데이터 페이지가 아닌 언두로그에서 데이터를 읽어와 잠금 없이 일관된 읽기가 가능하다는 것이다.
따라서 mysql은 잠금 사용을 최소화하여 설계 되어있기에 격리 수준에 따른 성능 차이가 크게 나타나지 않는 것이다.
serializable의 경우는 select 작업에도 잠금 처리를 하기에 처리 수준이 현저히 떨어지게 되는 것이다.
READ UNCOMMITED
다른 트랜잭션의 커밋 여부에 상관 없이 변경사항이 모두 보인다.
READ UNCOMMITED 수준에서는 모든 데이터를 데이터 페이지에서 참조한다.
이전에 Mysql 아키텍쳐에서 설명했듯이 트랜잭션의 변경 작업 발생시 변경 전 데이터를 언두로그에 기록해두고 데이터 페이지의 데이터를 변경시킨다. 커밋/롤백 여부에 상관 없이 데이터페이지에 먼저 기록을 한다.
따라서 데이터 페이지를 참조한다는 것은 아직 커밋되지 않는 다른 트랜잭션에 의한 변경사항도 모두 읽어들인다는 뜻이다.
커밋되지 않고 롤백 될 수 있는 데이터도 읽어들여 데이터의 일관성이 깨질 수 있는 현상을 더티리드라고 한다.
단순히 롤백될 수 있는 데이터만 참조하여 문제가 발생하는 것 뿐만 아니라 전체 완료된 작업이 아닌 부분 완료된 데이터를 참조하여 잘못된 결과를 초래할 수 도 있다.
Read Uncommited에서는 Non-Repeatable Read나 Phantom Read 또한 발생하는데 다음 격리수준에서 설명하겠다.
더티리드(Dirty Read)
트랜잭션이 아직 커밋되지 않은 데이터를 읽는 상황을 의미한다.
insert나 update된 데이터가 롤백 될 수 있기에 이를 참조하여 데이터를 변경했을 때 데이터의 일관성이 깨질 수 있다.
READ COMMITTED
이 수준에서는 커밋된 데이터만 참조하므로 더티리드는 발생하지 않는다.
커밋 완료된 데이터는 데이터 페이지를 참조하고, 진행중인 트랜잭션에 의한 변경 데이터는 언두로그를 참조한다.
데이터 페이지의 모든 레코드는 trx_id라는 자신을 변경시킨 트랜잭션 id를 갖는다.
해당 trx_id가 커밋 완료된 트랜잭션 목록에 포함되어 있는지 여부를 확인하여 커밋 완료되었다면 해당 레코드를 참조하고,
트랜잭션에 의해 변경 중이라면 언루 로그를 참조한다.
이와 같은 방식으로 항상 커밋된 데이터만 읽어오므로 커밋되지 않는 데이터를 읽는 더티리드 상황은 방지할 수 있다.
허나, 동일한 select 쿼리를 두 번 이상 수행했을 때, 결과가 달라지는 NON-REPEATABLE READ 현상이 발생할 수 있다.
트랜잭션 수행 중간에 커밋된 데이터가 보일 수 있기 때문에 커밋 시점 이전과 이후의 조회 결과가 다를 수가 있다.
트랜잭션 수행 중 다른 트랜잭션의 커밋 내용이 보인다는 것은 트랜잭션 내에서 select 쿼리를 수행하나 트랜잭션 범위 밖에서 수행하나 차이가 없다는 것을 의미한다.
NON-REPEATABLE READ (반복 불가능한 읽기)
Non-Repeatable Read란 하나의 트랜잭션 내에서 똑같은 select 쿼리로 조회 했을 때,
동일한 결과를 가져와야한다는 정합성에 어긋 나는 내용이다.
REPEATABLE READ
REPEATABLE READ 수준이 Mysql의 기본 격리 수준이다.
REPEATABLE READ는 나의 트랜잭션이 시작되기 전 커밋 완료된 데이터를 조회한다.
조회하는 모든 데이터는 나의 trx_id보다 작은 값을 갖는 레코드를 조회하게 된다.
데이터 페이지에 존재하는 레코드의 trx_id가 나의 트랜잭션 id 보다 크면 무조건 언두 로그를 참조하고,
나의 트랜잭션 id 보다 작다면 지금 변경 중인지 아니면 커밋 완료됬는지 여부를 판단하여
변경 중이라면 언두로그, 커밋 완료된 레코드라면 데이터 페이지를 참조한다.
trx_id가 나의 트랜잭션 id보다 작지만 변경 중이라는 것은 나의 트랜잭션 보다 일찍 시작했지만 아직 완료되지 않았다는 것을 의미한다.
완료되지 않은 데이터를 읽어들이면 안되기에 trx_id가 작더라도 커밋 완료여부를 판단한다.
트랜잭션 시작 전 커밋 완료된 데이터만 조회하므로 Non-Repeatable Read와 같은 상황은 발생하지 않는다.
또한 phantom read도 거의 발생하지 않는다.
phantom read
동일한 트랜잭션에서 select 쿼리를 2번 이상 수행했을 때, 존재하지 않은 데이터가 보이는 현상
잠금을 통한 select 시 잠금이 기존 레코드에 적용되어 insert 쿼리로 추가되는 상황은 막지 못하기에 발생한다.
Mysql innoDB의 REPATABLE READ는 잠금이 아닌 언두로그를 통해 데이터를 조회하므로 phantom read 현상이 발생하지 않는다.
트랜잭션 중간에 다른 트랜잭션에 의해 새로운 레코드가 insert 되더라도 여전히 나의 trx_id보다 작은 값만 읽어들이므로 phantom read가 발생하지 않는다.
또한, innoDB는 잠금을 사용하더라도 phantom read가 발생하지 않는다.
잠금을 사용한다느 것은 언두로그가 아닌 데이터 페이지의 레코드를 직접 읽는다는 것이다.
innoDB의 잠금에는 갭락이라는게 존재한다.
갭락은 레코드와 레코드 사이에 새로운 레코드가 insert 되는 것을 막아준다.
예를 들어 A 트랜잭션에서 select for update where id>10;을 수행하면 id가 10보다 큰 레코드에 갭락이 걸리고,
다른 트랜잭션에서 insert 쿼리를 수행하더라도 A 트랜잭션이 완료되기 전까지 대기해야한다.
따라서 innoDB에서는 잠금을 사용하던 잠금을 사용하지 않던 간에 phantom read가 발생하지 않는다.
예외적으로 발생되는 상황은 select 이후 select for update를 수행하는 경우이다.
처음에는 잠금 없이 언두로그를 사용하여 읽어 들이고 이후에는 잠금을 사용하여 데이터 페이지를 직접 읽어 들이면 insert된 데이터가 보여지게 된다.
정리하면 아래와 같다.
[언두로그만 사용하면 발생 안함]
- select 이후 select
[잠금 먼저 사용하면 발생안함]
- select for update 이후 select
- select for update 이후 select for update
[언두로그 사용 후 잠금 읽기는 발생함] :
- select 이후 select for update
SERIALIZABLE
InnoDB의 Select 쿼리는 위에서도 설명했듯이 아무런 잠금 처리가 되지 않는다.
serializable 수준에서는 select 작업도 잠금 처리가 되기에 phantom read가 발생하지 않는다.
모든 작업이 동시에 수행되는 것 없이 직렬화되어 순차적으로 수행되기에 동시성 처리가 상당히 떨어진다.
'DB' 카테고리의 다른 글
mysql 잠금 - 락의 종류 (0) | 2025.02.09 |
---|---|
mysql 아키텍쳐[2] - 버퍼풀 (0) | 2025.02.07 |
mysql 아키텍쳐[1] - 아키텍쳐, 언두로그, 리두로그 (0) | 2025.02.06 |
B-tree와 인덱스 (feat. mysql) (0) | 2021.11.07 |
데이터베이스 정규화(1차, 2차, 3차, BCNF 정규화) (0) | 2020.09.30 |