본문 바로가기
DB

mysql 트랜잭션 격리수준

by 코딩공장공장장 2025. 3. 3.

트랜잭션의 격리 수준이란 여러 트랜잭션이 동시에 처리 될 때, 다른 트랜잭션에 의해 영향을 받는 정도이다.

트랜잭션 격리 수준은 아래와 같이 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


이 수준에서는 커밋된 데이터만 참조하므로 더티 리드는 발생하지 않는다.

커밋 완료된 데이터는 데이터 페이지를 참조하고, 진행중인 트랜잭션에 의한 변경 데이터는 언두로그를 참조한다.

(커밋 완료 여부는 언두로그의 존재여부나 현재 진행 중인 트랜잭션 상태를 관리하는 별도의 테이블에서 참조 가능)

 

이와 같은 방식으로 항상 커밋된 데이터만 읽어오므로 커밋되지 않는 데이터를 읽는 더티리드 상황은 방지할 수 있다.

허나, 동일한 select 쿼리를 두 번 이상 수행했을 때, 결과가 달라지는 NON-REPEATABLE READ 현상이 발생할 수 있다.

트랜잭션 수행 중간에 커밋된 데이터가 보일 수 있기 때문에 커밋 시점 이전 이후의 조회결과가 다를 수가 있다.

트랜잭션 수행 중 다른 트랜잭션의 커밋 내용이 보인다는 것은 트랜잭션 내에서 select 쿼리를 수행하나 범위 밖에서 수행하나 차이가 없다는 것을 의미한다.

 

NON-REPEATABLE READ (반복 불가능한 읽기)

Non-Repeatable Read란 하나의 트랜잭션 내에서 똑같은 select 쿼리로 조회 했을 때,
동일한 결과를 가져와야한다는 정합성에 어긋 나는 내용이다.

 

REPEATABLE READ


REPEATABLE READ 수준이 Mysql의 기본 격리 수준이다.

REPEATABLE READ는 변경되지 않은 데이터는 데이터 페이지를 참조하고, 변경된 데이터는 나의 트랜잭션 id보다 작은 id값을 갖는 언두로그를 참조한다.

REPEATABLE READ는 나의 트랜잭션이 시작되기 전 커밋 완료된 데이터를 조회한다.

따라서 나의 트랜잭션 ID 보다 크면 무조건 언두로그를 참조하며,

작은 경우에는 커밋 완료 여부를 구분하여 언두로그 영역과 데이터 페이지 중 알맞은 곳에 접근한다.

 

트랜잭션 시작 전 커밋 완료된 데이터만 조회하므로 Non-Repeatable Read와 같은 상황은 발생하지 않는다.

또한 phantom read도 거의 발생하지 않는다.

 

phantom read

동일한 트랜잭션에서 select 쿼리를 2번 이상 수행했을 때, 존재하지 않은 데이터가 보이는 현상
잠금을 통한 select 시 잠금이 기존 데이터에는 적용되지만 insert 쿼리로 추가되는 레코드는 적용 되지 않기에 발생한다.

 

Mysql innoDB의 REPATABLE READ는 잠금이 아닌 언두로그를 통해 데이터를 조회하므로 phantom read 현상이 발생하지 않는다. 

허나, innoDB는 잠금을 사용하더라도 phantom read가 발생하지 않는다.

이는 갭락을 사용하기 때문인데 갭락을 걸면 레코드와 레코드 사이에 새로운 레코드가 insert 되는 것을 막아준다.

예를 들어 A 트랜잭션에서 select for update where id>10;을 수행하면 id가 10보다 큰 레코드에 갭락이 걸리고,

다른 트랜잭션에서 insert 쿼리를 수행하더라도 A 트랜잭션이 완료되기전까지 대기해야한다.

따라서 A 트랜잭션에서 이후에 select든 select for update든 select 쿼리를 수행하게 되면 다른 트랜잭션의 insert가 반영되지 못하므로 phantom read가 발생하지 않는다.

 

innoDB에서 phantom read가 발생되는 상황은 select 이후 select for update를 수행하는 경우이다.

처음에는 언두로그를 사용하고 이후에는 언두로그가 아닌 잠금을 사용하여 데이터페이지에 접근을 하니 레코드 추가를 막을 수 없다.

 

정리하면 아래와 같다.

 

[언두로그만 사용하면 발생 안함]

  • select 이후 select

[잠금 먼저 사용하면 발생안함]

  • select for update 이후 select 
  • select for update 이후 select for update

[언두로그 사용 후 잠금 읽기는 발생함]

  • select 이후 select for update

 

SERIALIZABLE


InnoDB의 Select 쿼리는 위에서도 설명했듯이 아무런 잠금 처리가 되지 않는다.

serializable 수준에서는 select 작업도 잠금 처리가 되기에 phantom read가 발생하지 않는다.
모든 작업이 동시에 수행되는 것 없이 직렬화되어 순차적으로 수행되기에 동시성 처리가 상당히 떨어진다.

 

 

반응형